;;; pdf-view.el --- View PDF documents. -*- lexical-binding:t -*-

;; Copyright (C) 2013  Andreas Politz

;; Author: Andreas Politz <politza@fh-trier.de>
;; Keywords: files, doc-view, pdf

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <http://www.gnu.org/licenses/>.

;;; Commentary:

;;  Functions related to viewing PDF documents.

;;; Code:

(require 'image-mode)
(require 'pdf-macs)
(require 'pdf-util)
(require 'pdf-info)
(require 'pdf-cache)
(require 'jka-compr)
(require 'bookmark)
(require 'password-cache)
(require 'cl-macs)

(declare-function cua-copy-region "cua-base")
(declare-function pdf-tools-pdf-buffer-p "pdf-tools")

(declare-function pdf-roll-scroll-forward "pdf-roll")
(declare-function pdf-roll-scroll-backward "pdf-roll")
(declare-function pdf-roll-next-page "pdf-roll")
(declare-function pdf-roll-redisplay "pdf-roll")
(declare-function pdf-roll-pre-redisplay "pdf-roll")
(declare-function pdf-roll-page-overlay "pdf-roll")
(declare-function pdf-roll-page-at-current-pos "pdf-roll")
(declare-function pdf-roll-display-image "pdf-roll")

(defvar pdf-view-roll-minor-mode nil)


;; * ================================================================== *
;; * Customizations
;; * ================================================================== *

(defgroup pdf-view nil
  "View PDF documents."
  :group 'pdf-tools)

(defcustom pdf-view-display-size 'fit-width
  "The desired size of displayed pages.

This may be one of `fit-height', `fit-width', `fit-page' or a
number as a scale factor applied to the document's size.  Any
other value behaves like `fit-width'."
  :group 'pdf-view
  :type '(choice number
                 (const fit-height)
                 (const fit-width)
                 (const fit-page)))

(make-variable-buffer-local 'pdf-view-display-size)

(defcustom pdf-view-resize-factor 1.25
  "Fractional amount of resizing of one resize command."
  :group 'pdf-view
  :type 'number)

(defcustom pdf-view-continuous t
  "In Continuous mode reaching the page edge advances to next/previous page.

When non-nil, scrolling a line upward at the bottom edge of the page
moves to the next page, and scrolling a line downward at the top edge
of the page moves to the previous page."
  :type 'boolean
  :group 'pdf-view)

(defcustom pdf-view-bounding-box-margin 0.05
  "Fractional margin used for slicing with the bounding-box."
  :group 'pdf-view
  :type 'number)

(defcustom pdf-view-use-imagemagick nil
  "Whether imagemagick should be used for rendering.

This variable has no effect, if imagemagick was not compiled into
Emacs or if imagemagick is the only way to display PNG images.
FIXME: Explain dis-/advantages of imagemagick and png."
  :group 'pdf-view
  :type 'boolean)

(defcustom pdf-view-use-scaling t
  "Whether images should be allowed to be scaled for rendering.

This variable affects both the reuse of higher-resolution images
as lower-resolution ones by down-scaling the image.  As well as
the rendering of higher-resolution for high-resolution displays,
if available."
  :group 'pdf-view
  :type 'boolean)

(defface pdf-view-region
  '((((background dark)) (:inherit region))
    (((background light)) (:inherit region)))
  "Face used to determine the colors of the region."
  :group 'pdf-view
  :group 'pdf-tools-faces)

(defface pdf-view-rectangle
  '((((background dark)) (:inherit highlight))
    (((background light)) (:inherit highlight)))
  "Face used to determine the colors of the highlighted rectangle."
  :group 'pdf-view
  :group 'pdf-tools-faces)

(defcustom pdf-view-midnight-colors '("#839496" . "#002b36" )
  "Colors used when command `pdf-view-midnight-minor-mode' is activated.

This should be a cons \(FOREGROUND . BACKGROUND\) of colors."
  :group 'pdf-view
  :type '(cons (color :tag "Foreground")
               (color :tag "Background")))

(defcustom pdf-view-midnight-invert t
  "In midnight mode invert the image color lightness maintaining hue.

This is particularly useful if you are viewing documents with
color coded data in plots.  This will maintain the colors such
that blue and red will remain these colors in the inverted
rendering. This inversion is non-trivial. This makes use of the
OKLab color space which is well calibrated to have equal
perceptual brightness across hue, but not all colors are within
the RGB gamut after inversion, causing some colors to saturate.
Nevertheless, this seems to work well in most cases."
  :group 'pdf-view
  :type 'boolean)

(defcustom pdf-view-midnight-gamma 1.0
  "In midnight mode, nonlinearly scale lightness.

Values less than 1 increase lightness, values more than 1 decrease
lightness (unless `pdf-view-midnight-gamma-before-invert' is non-nil, in
which reverses the effect direction)."
  :group 'pdf-view
  :type 'float)

(defcustom pdf-view-midnight-gamma-before-invert nil
  "In midnight mode, whether to scale lightness before inverting.

If non-nil, this inverts the direction of the effect of
`pdf-view-midnight-gamma', i.e. values more than 1 increase lightness
instead of decreasing it. This option is provided because it results in
different behaviors near the ends of the lightness scale. For example,
if this option is nil (the default), then a gamma values less than 1
significantly lighten colors very close to black. One gets a less
extreme effect by setting this option to non-nil and using gamma values
greater than 1."
  :group 'pdf-view
  :type 'boolean)

(defcustom pdf-view-change-page-hook nil
  "Hook run after changing to another page, but before displaying it.

See also `pdf-view-before-change-page-hook' and
`pdf-view-after-change-page-hook'."
  :group 'pdf-view
  :type 'hook)

(defcustom pdf-view-before-change-page-hook nil
  "Hook run before changing to another page.

See also `pdf-view-change-page-hook' and
`pdf-view-after-change-page-hook'."
  :group 'pdf-view
  :type 'hook)

(defcustom pdf-view-after-change-page-hook nil
  "Hook run after changing to and displaying another page.

See also `pdf-view-change-page-hook' and
`pdf-view-before-change-page-hook'."
  :group 'pdf-view
  :type 'hook)

(defcustom pdf-view-use-dedicated-register t
  "Whether to use dedicated register for PDF positions.

If this is non-nil, the commands `pdf-view-position-to-register'
and `pdf-view-jump-to-register' use the buffer-local variable
`pdf-view-register-alist' to store resp. retrieve marked
positions.  Otherwise the common variable `register-alist' is
used."
  :group 'pdf-view
  :type 'boolean)

(defcustom pdf-view-image-relief 0
  "Add a shadow rectangle around the page's image.

See :relief property in Info node `(elisp) Image Descriptors'."
  :group 'pdf-view
  :type '(integer :tag "Pixel")
  :link '(info-link "(elisp) Image Descriptors"))

(defcustom pdf-view-max-image-width 4800
  "Maximum width of any image displayed in pixel."
  :group 'pdf-view
  :type '(integer :tag "Pixel"))

(defcustom pdf-view-use-unicode-ligther t
  "Decide whether to use unicode symbols in the mode-line.

On some systems finding a font which supports those symbols can
take some time.  If you don't want to spend that time waiting and
don't care for a nicer looking mode-line, set this variable to
nil.

Note, that this option has only an effect when this library is
loaded."
  :group 'pdf-view
  :type 'boolean)

(defcustom pdf-view-incompatible-modes
  '(linum-mode linum-relative-mode helm-linum-relative-mode
	           nlinum-mode nlinum-hl-mode nlinum-relative-mode yalinum-mode
               display-line-numbers-mode)
  "A list of modes incompatible with `pdf-view-mode'.

Issue a warning, if one of them is active in a PDF buffer."
  :group 'pdf-view
  :type '(repeat symbol))

(defcustom pdf-view-selection-style 'word
  "The current default selection style.

Must be one of `glyph', `word', or `line'."
  :group 'pdf-view
  :type '(choice (const glyph)
                 (const word)
                 (const line)))

(defcustom pdf-view-mode-line-position-prefix "P"
  "Prefix for page number shown in the mode line."
  :group 'pdf-view
  :type 'string)

(defcustom pdf-view-mode-line-position-use-labels nil
  "Whether current page should come from page labels."
  :group 'pdf-view
  :type 'boolean)


;; * ================================================================== *
;; * Internal variables and macros
;; * ================================================================== *

(defvar-local pdf-view-active-region nil
  "The active region as a cons cell of page and list of edges.

Edge values are relative coordinates.")

(defvar-local pdf-view--have-rectangle-region nil
  "Non-nil if the region is currently rendered as a rectangle.

This variable is set in `pdf-view-mouse-set-region' and used in
`pdf-view-mouse-extend-region' to determine the right choice
regarding display of the region in the later function.")

(defvar-local pdf-view--buffer-file-name nil
  "Local copy of remote file or nil.")

(defvar-local pdf-view--server-file-name nil
  "The servers notion of this buffer's filename.")

(defvar-local pdf-view--next-page-timer nil
  "Timer used in `pdf-view-next-page-command'.")

(defvar-local pdf-view--hotspot-functions nil
  "Alist of hotspot functions.")

(defvar-local pdf-view--current-rotation nil
  "Current rotation of the page.")

(defvar-local pdf-view-register-alist nil
  "Local, dedicated register for PDF positions.")

(defun pdf-view-current-pagelabel (&optional window)
  (nth (1- (pdf-view-current-page window)) (pdf-info-pagelabels)))

(defun pdf-view-active-region-p nil
  "Return t if there are active regions."
  (not (null pdf-view-active-region)))

(defmacro pdf-view-assert-active-region ()
  "Signal an error if there are no active regions."
  `(unless (pdf-view-active-region-p)
     (error "The region is not active")))

(defconst pdf-view-have-image-mode-pixel-vscroll
  (>= emacs-major-version 27)
  "Whether `image-mode' scrolls vertically by pixels.")


;; * ================================================================== *
;; * Major Mode
;; * ================================================================== *

(defvar pdf-view-mode-map
  (let ((map (make-sparse-keymap)))
    (set-keymap-parent map image-mode-map)
    (define-key map (kbd "Q")         'kill-current-buffer)
    ;; Navigation in the document
    (define-key map (kbd "n")         'pdf-view-next-page-command)
    (define-key map (kbd "p")         'pdf-view-previous-page-command)
    (define-key map (kbd "<next>")    'forward-page)
    (define-key map (kbd "<prior>")   'backward-page)
    (define-key map [remap forward-page]  'pdf-view-next-page-command)
    (define-key map [remap backward-page] 'pdf-view-previous-page-command)
    (define-key map (kbd "SPC")       'pdf-view-scroll-up-or-next-page)
    (define-key map [remap scroll-up-command] #'pdf-view-scroll-up-or-next-page)
    (define-key map [remap scroll-down-command] #'pdf-view-scroll-down-or-previous-page)
    (define-key map (kbd "S-SPC")     'pdf-view-scroll-down-or-previous-page)
    (define-key map (kbd "DEL")       'pdf-view-scroll-down-or-previous-page)
    (define-key map (kbd "C-n")       'pdf-view-next-line-or-next-page)
    (define-key map (kbd "<down>")    'pdf-view-next-line-or-next-page)
    (define-key map [remap next-line] 'pdf-view-next-line-or-next-page)
    (define-key map (kbd "C-p")           'pdf-view-previous-line-or-previous-page)
    (define-key map (kbd "<up>")          'pdf-view-previous-line-or-previous-page)
    (define-key map [remap previous-line] 'pdf-view-previous-line-or-previous-page)
    (define-key map (kbd "M-<")                 'pdf-view-first-page)
    (define-key map [remap beginning-of-buffer] 'pdf-view-first-page)
    (define-key map (kbd "M->")                 'pdf-view-last-page)
    (define-key map [remap end-of-buffer]       'pdf-view-last-page)
    (define-key map [remap goto-line] 'pdf-view-goto-page)
    (define-key map (kbd "M-g l")     'pdf-view-goto-label)
    (define-key map (kbd "RET")       'image-next-line)
    ;; Zoom in/out.
    (define-key map "+"               'pdf-view-enlarge)
    (define-key map "="               'pdf-view-enlarge)
    (define-key map "-"               'pdf-view-shrink)
    (define-key map "0"               'pdf-view-scale-reset)
    ;; Fit the image to the window
    (define-key map "W"               'pdf-view-fit-width-to-window)
    (define-key map "H"               'pdf-view-fit-height-to-window)
    (define-key map "P"               'pdf-view-fit-page-to-window)
    ;; Slicing the image
    (define-key map (kbd "s m")       'pdf-view-set-slice-using-mouse)
    (define-key map (kbd "s b")       'pdf-view-set-slice-from-bounding-box)
    (define-key map (kbd "s r")       'pdf-view-reset-slice)
    (define-key map (kbd "s c")       'pdf-view-set-slice-common-bounding-box)
    ;; Rotation.
    (define-key map (kbd "R")              #'pdf-view-rotate)
    ;; Reconvert
    (define-key map (kbd "C-c C-c")   'doc-view-mode)
    (define-key map (kbd "g")         'revert-buffer)
    ;; Region
    (define-key map [down-mouse-1] 'pdf-view-mouse-set-region)
    (define-key map [M-down-mouse-1] 'pdf-view-mouse-set-region-rectangle)
    (define-key map [C-down-mouse-1] 'pdf-view-mouse-extend-region)
    (define-key map [remap kill-region] 'pdf-view-kill-ring-save)
    (define-key map [remap kill-ring-save] 'pdf-view-kill-ring-save)
    (define-key map [remap mark-whole-buffer] 'pdf-view-mark-whole-page)
    ;; Other
    (define-key map (kbd "C-c C-d") 'pdf-view-dark-minor-mode)
    (define-key map (kbd "m") 'pdf-view-position-to-register)
    (define-key map (kbd "'") 'pdf-view-jump-to-register)

    (define-key map (kbd "C-c C-i") 'pdf-view-extract-region-image)
    ;; Rendering
    (define-key map (kbd "C-c C-r m") 'pdf-view-midnight-minor-mode)
    (define-key map (kbd "C-c C-r t") 'pdf-view-themed-minor-mode)
    (define-key map (kbd "C-c C-r p") 'pdf-view-printer-minor-mode)
    map)
  "Keymap used by `pdf-view-mode' when displaying a doc as a set of images.")

(defvar pdf-tools-enabled-modes)
(define-derived-mode pdf-view-mode special-mode "PDFView"
  "Major mode in PDF buffers.

PDFView Mode is an Emacs PDF viewer.  It displays PDF files as
PNG images in Emacs buffers."
  :group 'pdf-view
  :abbrev-table nil
  :syntax-table nil
  ;; Setup a local copy for remote files.
  (when (and (or jka-compr-really-do-compress
                 (let ((file-name-handler-alist nil))
                   (not (and buffer-file-name
                             (file-readable-p buffer-file-name)))))
             (pdf-tools-pdf-buffer-p))
    (let ((tempfile (pdf-util-make-temp-file)))
      (write-region nil nil tempfile nil 'no-message)
      (setq-local pdf-view--buffer-file-name tempfile)))
  ;; Decryption needs to be done before any other function calls into
  ;; pdf-info.el (e.g. from the mode-line during redisplay during
  ;; waiting for process output).
  (pdf-view-decrypt-document)

  ;; Setup scroll functions
  (if (boundp 'mwheel-scroll-up-function) ; not --without-x build
      (setq-local mwheel-scroll-up-function
                  #'pdf-view-scroll-up-or-next-page))
  (if (boundp 'mwheel-scroll-down-function)
      (setq-local mwheel-scroll-down-function
                  #'pdf-view-scroll-down-or-previous-page))

  (if (boundp 'mwheel-scroll-left-function)
      (setq-local mwheel-scroll-left-function
                  #'image-scroll-left))

  (if (boundp 'mwheel-scroll-right-function)
      (setq-local mwheel-scroll-right-function
                  #'image-scroll-right))

  ;; Disable pixel-scroll-precision-mode locally if enabled
  (if (bound-and-true-p pixel-scroll-precision-mode)
      (set (make-local-variable 'pixel-scroll-precision-mode) nil))
  (if (boundp 'mwheel-coalesce-scroll-events)
      (setq-local mwheel-coalesce-scroll-events t))

  ;; If the Emacs theme is dark, add `pdf-view-dark-minor-mode' to the
  ;; list of `pdf-tools-enabled-modes'. See an interesting discussion
  ;; at: https://github.com/vedang/pdf-tools/issues/166 about how this
  ;; avoids a segfault crash in MacOS Ventura. IF you know why this
  ;; happens, please get in touch via the linked issue.

  (when (eq 'dark (frame-parameter nil 'background-mode))
    (add-to-list 'pdf-tools-enabled-modes 'pdf-view-dark-minor-mode))

  ;; Clearing overlays
  (add-hook 'change-major-mode-hook
            (lambda ()
              (remove-overlays (point-min) (point-max) 'pdf-view t))
            nil t)
  (remove-overlays (point-min) (point-max) 'pdf-view t) ;Just in case.

  ;; Setup other local variables.
  (setq-local mode-line-position
              '(" " pdf-view-mode-line-position-prefix
                ;; Show page label when enabled and available,
                ;; otherwise show numeric page. Guard against errors.
                (:eval
                 (or (and pdf-view-mode-line-position-use-labels
                          (ignore-errors (pdf-view-current-pagelabel)))
                     (number-to-string (pdf-view-current-page))))
                ;; Avoid errors during redisplay.
                "/" (:eval (or (ignore-errors
                                 (number-to-string (pdf-cache-number-of-pages)))
                               "???"))))
  (setq-local auto-hscroll-mode nil)
  (setq-local pdf-view--server-file-name (pdf-view-buffer-file-name))
  ;; High values of scroll-conservatively seem to trigger
  ;; some display bug in xdisp.c:try_scrolling .
  (setq-local scroll-conservatively 0)
  (setq-local cursor-type nil)
  (setq-local buffer-read-only t)
  (setq-local view-read-only nil)
  (setq-local bookmark-make-record-function
              'pdf-view-bookmark-make-record)
  (setq-local revert-buffer-function #'pdf-view-revert-buffer)
  ;; No auto-save at the moment.
  (setq-local buffer-auto-save-file-name nil)
  ;; Disable image rescaling.
  (when (boundp 'image-scaling-factor)
    (setq-local image-scaling-factor 1))
  ;; No undo at the moment.
  (unless buffer-undo-list
    (buffer-disable-undo))
  ;; Enable transient-mark-mode, so region deactivation when quitting
  ;; will work.
  (setq-local transient-mark-mode t)

  (add-hook 'window-configuration-change-hook
            'pdf-view-redisplay-some-windows nil t)
  (add-hook 'deactivate-mark-hook 'pdf-view-deactivate-region nil t)
  (add-hook 'write-contents-functions
            'pdf-view--write-contents-function nil t)
  (add-hook 'kill-buffer-hook 'pdf-view-close-document nil t)
  (pdf-view-add-hotspot-function
   'pdf-view-text-regions-hotspots-function -9)

  ;; Keep track of display info
  (add-hook 'image-mode-new-window-functions
            'pdf-view-new-window-function nil t)
  (image-mode-setup-winprops)

  ;; Issue a warning in the future about incompatible modes.
  (run-with-timer 1 nil (lambda (buffer)
                          (when (buffer-live-p buffer)
                            (pdf-view-check-incompatible-modes buffer)))
		  (current-buffer)))

(advice-add 'cua-copy-region
	        :before-until
	        #'cua-copy-region--pdf-view-advice)

(defun cua-copy-region--pdf-view-advice (&rest _)
  "If the current buffer is in `pdf-view' mode, call `pdf-view-kill-ring-save'."
  (when (eq major-mode 'pdf-view-mode)
    (pdf-view-kill-ring-save)
    t))

(defun pdf-view-check-incompatible-modes (&optional buffer)
  "Check BUFFER for incompatible modes, maybe issue a warning."
  (with-current-buffer (or buffer (current-buffer))
    (let ((modes
	   (cl-remove-if-not
	    (lambda (mode) (and (symbolp mode)
				(boundp mode)
				(symbol-value mode)))
	    pdf-view-incompatible-modes)))
      (when modes
	(display-warning
	 'pdf-view
	 (format "These modes are incompatible with `pdf-view-mode',
	please deactivate them (or customize pdf-view-incompatible-modes): %s"
		 (mapconcat #'symbol-name modes ",")))))))

(defun pdf-view-decrypt-document ()
  "Read a password, if the document is encrypted and open it."
  (interactive)
  (when (pdf-info-encrypted-p)
    (let ((fn (buffer-file-name))
          (prompt "Enter password for pdf document: ")
          (i 3)
          key password)

      (when fn
        (setq prompt (format "Enter password for `%s': "
                             (abbreviate-file-name fn)))
        (setq key (concat "/pdf-tools" fn))
        ;; First, try with a cached password
        (when (setq password (password-read-from-cache key))
          (ignore-errors (pdf-info-open nil password))
          (when (pdf-info-encrypted-p)
            (password-cache-remove key))))

      (while (and (> i 0)
                  (pdf-info-encrypted-p))
        (setq i (1- i))
        ;; Cached password was not present or valid, try reading a new password
        ;; without cache.
        (setq password (password-read prompt))
        (setq prompt "Invalid password, try again: ")
        (ignore-errors (pdf-info-open nil password)))
      (pdf-info-open nil password)
      (when key
        (password-cache-add key password))))
  nil)

(defun pdf-view-buffer-file-name ()
  "Return the local filename of the PDF in the current buffer.

This may be different from variable `buffer-file-name' when
operating on a local copy of a remote file."
  (or pdf-view--buffer-file-name
      (buffer-file-name)
      (buffer-file-name (buffer-base-buffer))))

(defun pdf-view--write-contents-function ()
  "Function for `write-contents-functions' to save the buffer."
  (when (pdf-util-pdf-buffer-p)
    (let ((tempfile (pdf-info-save pdf-view--server-file-name)))
      (unwind-protect
          (progn
            ;; Order matters here: We need to first copy the new
            ;; content (tempfile) to the PDF, and then close the PDF.
            ;; Since while closing the file (and freeing its resources
            ;; in the process), it may be immediately reopened due to
            ;; redisplay happening inside the pdf-info-close function
            ;; (while waiting for a response from the process.).
            (copy-file tempfile (or (buffer-file-name)
                                    (read-file-name
                                     "File name to save PDF to: "))
                       t)
            (pdf-info-close pdf-view--server-file-name)

            (when pdf-view--buffer-file-name
              (copy-file tempfile pdf-view--buffer-file-name t))
            (clear-visited-file-modtime)
            (set-buffer-modified-p nil)
            (setq pdf-view--server-file-name
                  (pdf-view-buffer-file-name))
            t)
        (when (file-exists-p tempfile)
          (delete-file tempfile))))))

(defun pdf-view--after-revert ()
  "Reload the local copy in case of a remote file, and close the document."
  (when pdf-view--buffer-file-name
    (write-region nil nil pdf-view--buffer-file-name nil 'no-message))
  (pdf-info-close))

(defun pdf-view-revert-buffer (&optional ignore-auto noconfirm)
  "Revert buffer while preserving current modes.

Optional parameters IGNORE-AUTO and NOCONFIRM are defined as in
`revert-buffer'."
  (interactive (list (not current-prefix-arg)))
  ;; Bind to default so that we can use pdf-view-revert-buffer as
  ;; revert-buffer-function.  A binding of nil is needed in Emacs 24.3, but in
  ;; later versions the semantics that nil means the default function should
  ;; not relied upon.
  (let ((revert-buffer-function (when (fboundp #'revert-buffer--default)
                                  #'revert-buffer--default))
        (after-revert-hook
         (cons #'pdf-view--after-revert
               after-revert-hook)))
    (prog1
        (revert-buffer ignore-auto noconfirm 'preserve-modes)
      (pdf-view-decrypt-document)
      (pdf-view-redisplay t))))

(defun pdf-view-close-document ()
  "Return immediately after closing document.

This function always succeeds.  See also `pdf-info-close', which
does not return immediately."
  (when (pdf-info-running-p)
    (let ((pdf-info-asynchronous 'ignore))
      (ignore-errors
        (pdf-info-close)))))


;; * ================================================================== *
;; * Scaling
;; * ================================================================== *

(defun pdf-view-fit-page-to-window ()
  "Fit PDF to window.

Choose the larger of PDF's height and width, and fits that
dimension to window."
  (interactive)
  (setq pdf-view-display-size 'fit-page)
  (image-set-window-vscroll 0)
  (image-set-window-hscroll 0)
  (pdf-view-redisplay t))

(defun pdf-view-fit-height-to-window ()
  "Fit PDF height to window."
  (interactive)
  (setq pdf-view-display-size 'fit-height)
  (image-set-window-vscroll 0)
  (pdf-view-redisplay t))

(defun pdf-view-fit-width-to-window ()
  "Fit PDF size to window."
  (interactive)
  (setq pdf-view-display-size 'fit-width)
  (image-set-window-hscroll 0)
  (pdf-view-redisplay t))

(defun pdf-view-enlarge (factor)
  "Enlarge PDF by FACTOR.

When called interactively, uses the value of
`pdf-view-resize-factor'.

For example, (pdf-view-enlarge 1.25) increases size by 25%."
  (interactive
   (list (float pdf-view-resize-factor)))
  (let* ((size (pdf-view-image-size))
         (pagesize (pdf-cache-pagesize
                    (pdf-view-current-page)))
         (scale (/ (float (car size))
                   (float (car pagesize)))))
    (setq pdf-view-display-size
          (* factor scale))
    (pdf-view-redisplay t)))

(defun pdf-view-shrink (factor)
  "Shrink PDF by FACTOR.

When called interactively, uses the value of
`pdf-view-resize-factor'.

For example, (pdf-view-shrink 1.25) decreases size by 20%."
  (interactive
   (list (float pdf-view-resize-factor)))
  (pdf-view-enlarge (/ 1.0 factor)))

(defun pdf-view-scale-reset ()
  "Reset PDF to its default set."
  (interactive)
  (setq pdf-view-display-size 1.0)
  (pdf-view-redisplay t))


;; * ================================================================== *
;; * Rotation
;; * ================================================================== *
(defun pdf-view-rotate (angle)
  "Rotate the current page by ANGLE degrees clockwise.
When called interactively, angle defaults to 90.  Moreover, if
called interactively with a prefix argument, then rotate
anti-clockwise."
  (interactive (list (if current-prefix-arg -90 90)))
  (setq-local pdf-view--current-rotation
              (mod (+ (or pdf-view--current-rotation 0)
                      angle)
                   360))
  (pdf-view-redisplay t))


;; * ================================================================== *
;; * Moving by pages and scrolling
;; * ================================================================== *

(defvar pdf-view-inhibit-redisplay nil)
(defvar pdf-view-inhibit-hotspots nil)

(defun pdf-view-goto-page (page &optional window)
  "Go to PAGE in PDF.

If optional parameter WINDOW, go to PAGE in all `pdf-view'
windows."
  (interactive
   (list (if current-prefix-arg
             (prefix-numeric-value current-prefix-arg)
           (read-number "Page: "))))
  (unless (and (>= page 1)
               (<= page (pdf-cache-number-of-pages)))
    (error "No such page: %d" page))
  (unless window
    (setq window
          (if (pdf-util-pdf-window-p)
              (selected-window)
            t)))
  (save-selected-window
    ;; Select the window for the hooks below.
    (when (window-live-p window)
      (select-window window 'norecord))
    (let ((changing-p
           (not (eq page (pdf-view-current-page window)))))
      (when changing-p
        (run-hooks 'pdf-view-before-change-page-hook)
        (setf (pdf-view-current-page window) page)
        (run-hooks 'pdf-view-change-page-hook))
      (when (window-live-p window)
        (image-set-window-vscroll 0)
        (if pdf-view-roll-minor-mode
            (pdf-roll-pre-redisplay window)
          (pdf-view-redisplay window)))
      (when changing-p
        (pdf-view-deactivate-region)
        (force-mode-line-update)
        (run-hooks 'pdf-view-after-change-page-hook))))
  nil)

(defun pdf-view-next-page (&optional n)
  "View the next page in the PDF.

Optional parameter N moves N pages forward."
  (interactive "p")
  (pdf-view-goto-page (+ (pdf-view-current-page)
                         (or n 1))))

(defun pdf-view-previous-page (&optional n)
  "View the previous page in the PDF.

Optional parameter N moves N pages backward."
  (interactive "p")
  (pdf-view-next-page (- (or n 1))))

(defun pdf-view-next-page-command (&optional n)
  "View the next page in the PDF.

Optional parameter N moves N pages forward.

This command is a wrapper for `pdf-view-next-page' that will
indicate to the user if they are on the last page and more."
  (declare (interactive-only pdf-view-next-page))
  (interactive "p")
  (unless n (setq n 1))
  (when (> (+ (pdf-view-current-page) n)
           (pdf-cache-number-of-pages))
    (user-error "Last page"))
  (when (< (+ (pdf-view-current-page) n) 1)
    (user-error "First page"))
  (let ((pdf-view-inhibit-redisplay t))
    (pdf-view-goto-page
     (+ (pdf-view-current-page) n)))
  (force-mode-line-update)
  (sit-for 0)
  (when pdf-view--next-page-timer
    (cancel-timer pdf-view--next-page-timer)
    (setq pdf-view--next-page-timer nil))
  (if (or (not (input-pending-p))
          (and (> n 0)
               (= (pdf-view-current-page)
                  (pdf-cache-number-of-pages)))
          (and (< n 0)
               (= (pdf-view-current-page) 1)))
      (pdf-view-redisplay)
    (setq pdf-view--next-page-timer
          (run-with-idle-timer 0.001 nil 'pdf-view-redisplay (selected-window)))))

(defun pdf-view-previous-page-command (&optional n)
  "View the previous page in the PDF.

Optional parameter N moves N pages backward.

This command is a wrapper for `pdf-view-previous-page'."
  (declare (interactive-only pdf-view-previous-page))
  (interactive "p")
  (with-no-warnings
    (pdf-view-next-page-command (- (or n 1)))))

(defun pdf-view-first-page ()
  "View the first page."
  (interactive)
  (pdf-view-goto-page 1))

(defun pdf-view-last-page ()
  "View the last page."
  (interactive)
  (pdf-view-goto-page (pdf-cache-number-of-pages)))

(defun pdf-view-scroll-up-or-next-page (&optional arg)
  "Scroll page up ARG lines if possible, else go to the next page.

When `pdf-view-continuous' is non-nil, scrolling upward at the
bottom edge of the page moves to the next page.  Otherwise, go to
next page only on typing SPC (ARG is nil)."
  (interactive "P")
  (if (or pdf-view-continuous (null arg))
      (let ((hscroll (window-hscroll))
            (cur-page (pdf-view-current-page))
            (win-scroll (window-vscroll nil pdf-view-have-image-mode-pixel-vscroll))
            (img-scroll (image-scroll-up arg)))
        (when (or
               ;; There is no next line for the image to scroll to
               (and img-scroll (= win-scroll img-scroll))
               ;; Workaround rounding/off-by-one issues.
               (memq pdf-view-display-size
                     '(fit-height fit-page)))
          (pdf-view-next-page)
          (when (/= cur-page (pdf-view-current-page))
            (image-bob)
            (image-bol 1))
          (image-set-window-hscroll hscroll)))
    (image-scroll-up arg)))

(defun pdf-view-scroll-down-or-previous-page (&optional arg)
  "Scroll page down ARG lines if possible, else go to the previous page.

When `pdf-view-continuous' is non-nil, scrolling downward at the
top edge of the page moves to the previous page.  Otherwise, go
to previous page only on typing DEL (ARG is nil)."
  (interactive "P")
  (if (or pdf-view-continuous (null arg))
      (let ((hscroll (window-hscroll))
            (cur-page (pdf-view-current-page))
            (win-scroll (window-vscroll nil pdf-view-have-image-mode-pixel-vscroll))
            (img-scroll (image-scroll-down arg)))
        (when (or
               ;; There is no previous line for the image to scroll to
               (and img-scroll (= win-scroll img-scroll))
               ;; Workaround rounding/off-by-one issues.
               (memq pdf-view-display-size
                     '(fit-height fit-page)))
          (pdf-view-previous-page)
          (when (/= cur-page (pdf-view-current-page))
            (image-eob)
            (image-bol 1))
          (image-set-window-hscroll hscroll)))
    (image-scroll-down arg)))

(defun pdf-view--next-line-or-next-page (&optional arg)
  "Scroll upward by ARG lines if possible, else go to the next page.

When `pdf-view-continuous' is non-nil, scrolling a line upward
at the bottom edge of the page moves to the next page."
  (interactive "p")
  (if pdf-view-continuous
      (let ((hscroll (window-hscroll))
            (cur-page (pdf-view-current-page)))
        (when (= (window-vscroll nil pdf-view-have-image-mode-pixel-vscroll)
                 (image-next-line arg))
          (ignore-errors (pdf-view-next-page))
          (when (/= cur-page (pdf-view-current-page))
            (image-bob)
            (image-bol 1))
          (image-set-window-hscroll hscroll)))
    (image-next-line arg)))

(defun pdf-view-next-line-or-next-page (&optional arg)
  (interactive "p")
  (if pdf-view-roll-minor-mode
      (dotimes (_ (or arg 1)) (pdf-roll-scroll-forward))
    (pdf-view--next-line-or-next-page arg)))

(defun pdf-view--previous-line-or-previous-page (&optional arg)
  "Scroll downward by ARG lines if possible, else go to the previous page.

When `pdf-view-continuous' is non-nil, scrolling a line downward
at the top edge of the page moves to the previous page."
  (interactive "p")
  (if pdf-view-continuous
      (let ((hscroll (window-hscroll))
            (cur-page (pdf-view-current-page)))
        (when (= (window-vscroll nil pdf-view-have-image-mode-pixel-vscroll)
                 (image-previous-line arg))
          (ignore-errors (pdf-view-previous-page))
          (when (/= cur-page (pdf-view-current-page))
            (image-eob)
            (image-bol 1))
          (image-set-window-hscroll hscroll)))
    (image-previous-line arg)))

(defun pdf-view-previous-line-or-previous-page (&optional arg)
  (interactive "p")
  (if pdf-view-roll-minor-mode
      (dotimes (_ (or arg 1)) (pdf-roll-scroll-backward))
    (pdf-view--previous-line-or-previous-page arg)))

(defun pdf-view-goto-label (label)
  "Go to the page corresponding to LABEL.

Usually, the label of a document's page is the same as its
displayed page number."
  (interactive
   (list (let ((labels (pdf-info-pagelabels)))
           (completing-read "Goto label: " labels nil t))))
  (let ((index (cl-position label (pdf-info-pagelabels) :test 'equal)))
    (unless index
      (error "No such label: %s" label))
    (pdf-view-goto-page (1+ index))))

(defun pdf-view-center-in-window ()
  "Center PDF in window horizontally."
  (interactive)
  (image-set-window-hscroll
   (/ (* (- (car (pdf-view-image-size))
            (window-pixel-width))
         (window-width))
      2 (window-pixel-width))) ; convert from pixel to character width
  (pdf-view-redisplay t))

(defun pdf-view-align-left ()
  "Align left edge of pdf with left edge of window."
  (interactive)
  (image-set-window-hscroll 0)
  (pdf-view-redisplay t))

(defun pdf-view-align-right ()
  "Align right edge of pdf with right edge of window."
  (interactive)
  (image-set-window-hscroll
   (/ (* (- (car (pdf-view-image-size))
            (window-pixel-width))
         (window-width))
      (window-pixel-width))) ; convert from pixel to character width
  (pdf-view-redisplay t))


;; * ================================================================== *
;; * Slicing
;; * ================================================================== *

(defun pdf-view-set-slice (x y width height &optional window)
  "Set the slice of the pages that should be displayed in WINDOW.

WINDOW defaults to `selected-window' if not provided.
X, Y, WIDTH and HEIGHT should be relative coordinates, i.e. in
\[0;1\].  To reset the slice use `pdf-view-reset-slice'."
  (unless (equal (pdf-view-current-slice window)
                 (list x y width height))
    (setf (pdf-view-current-slice window)
          (mapcar (lambda (v)
                    (max 0 (min 1 v)))
                  (list x y width height)))
    (pdf-view-redisplay window)))

(defun pdf-view-set-slice-using-mouse ()
  "Set the slice of the images that should be displayed.

Set the slice by pressing `mouse-1' at its top-left corner and
dragging it to its bottom-right corner.  See also
`pdf-view-set-slice' and `pdf-view-reset-slice'."
  (interactive)
  (let ((size (pdf-view-image-size))
        x y w h done)
    (while (not done)
      (let ((e (read-event
                (concat "Press mouse-1 at the top-left corner and "
                        "drag it to the bottom-right corner!"))))
        (when (eq (car e) 'drag-mouse-1)
          (setq x (car (posn-object-x-y (event-start e))))
          (setq y (cdr (posn-object-x-y (event-start e))))
          (setq w (- (car (posn-object-x-y (event-end e))) x))
          (setq h (- (cdr (posn-object-x-y (event-end e))) y))
          (setq done t))))
    (apply 'pdf-view-set-slice
           (pdf-util-scale
            (list x y w h)
            (cons (/ 1.0 (float (car size)))
                  (/ 1.0 (float (cdr size))))))))

(defun pdf-view--bounding-box-to-slice (bb)
  "Convert bounding box BB to slice format for `pdf-view-set-slice'.
BB is a list (LEFT TOP RIGHT BOTTOM).
Returns a list (X Y WIDTH HEIGHT)."
  (let* ((margin (max 0 (or pdf-view-bounding-box-margin 0)))
         (halfmargin (/ margin 2)))
    (cl-destructuring-bind (left top right bottom) bb
      (list (- left halfmargin) (- top halfmargin)
            (+ (- right left) margin) (+ (- bottom top) margin)))))

(defun pdf-view-set-slice-from-bounding-box (&optional window)
  "Set the slice from the page's bounding-box.

WINDOW defaults to `selected-window' if not provided.

The result is that the margins are almost completely cropped,
much more accurate than could be done manually using
`pdf-view-set-slice-using-mouse'.

See also `pdf-view-bounding-box-margin'."
  (interactive)
  (let* ((bb (pdf-cache-boundingbox (pdf-view-current-page window)))
         (slice (pdf-view--bounding-box-to-slice bb)))
    (apply 'pdf-view-set-slice
           (append slice (and window (list window))))))

(defun pdf-document-common-bounding-box (&optional file-or-buffer)
  "Return the common bounding box for all pages in FILE-OR-BUFFER.
Returns a list (LEFT TOP RIGHT BOTTOM)."
  (let ((left 1.0)
        (top 1.0)
        (right 0.0)
        (bottom 0.0))
    (dotimes (i (pdf-info-number-of-pages file-or-buffer))
      (cl-destructuring-bind (b-left b-top b-right b-bottom) (pdf-info-boundingbox (1+ i) file-or-buffer)
        (setq left (min left b-left)
              top (min top b-top)
              right (max right b-right)
              bottom (max bottom b-bottom))))
    (list left top right bottom)))

(defun pdf-view-set-slice-common-bounding-box (&optional window)
  "Set the slice from the common bounding box of all pages.

WINDOW specifies which document to use; defaults to `selected-window'.
A margin is added from `pdf-view-bounding-box-margin'."
  (interactive)
  (let* ((bb (pdf-document-common-bounding-box))
         (slice (pdf-view--bounding-box-to-slice bb)))
    (when window
      (push slice window))
    (message (prin1-to-string slice))
    (apply 'pdf-view-set-slice slice)))

(defun pdf-view-reset-slice (&optional window)
  "Reset the current slice and redisplay WINDOW.

WINDOW defaults to `selected-window' if not provided.

After calling this function the whole page will be visible
again."
  (interactive)
  (when (pdf-view-current-slice window)
    (setf (pdf-view-current-slice window) nil)
    (pdf-view-redisplay window))
  nil)

(define-minor-mode pdf-view-auto-slice-minor-mode
  "Automatically slice pages according to their bounding boxes.

See also `pdf-view-set-slice-from-bounding-box'."
  :group 'pdf-view
  (pdf-util-assert-pdf-buffer)
  (cond
   (pdf-view-auto-slice-minor-mode
    (dolist (win (get-buffer-window-list nil nil t))
      (when (pdf-util-pdf-window-p win)
        (pdf-view-set-slice-from-bounding-box win)))
    (add-hook 'pdf-view-change-page-hook
              'pdf-view-set-slice-from-bounding-box nil t))
   (t
    (progn (remove-hook 'pdf-view-change-page-hook
                        'pdf-view-set-slice-from-bounding-box t)
           (pdf-view-reset-slice)))))


;; * ================================================================== *
;; * Display
;; * ================================================================== *

(defun pdf-view-image-type ()
  "Return the image type that should be used.

The return value is either `imagemagick' (if available and wanted
or if png is not available) or `png'.

Signal an error, if neither `imagemagick' nor `png' is available.

See also `pdf-view-use-imagemagick'."
  (cond ((and pdf-view-use-imagemagick
              (fboundp 'imagemagick-types))
         'imagemagick)
        ((image-type-available-p 'image-io)
         'image-io)
        ((image-type-available-p 'png)
         'png)
        ((fboundp 'imagemagick-types)
         'imagemagick)
        (t
         (error "PNG image supported not compiled into Emacs"))))

(defmacro pdf-view-create-image (data &rest props)
  ;; TODO: add DATA and PROPS to docstring.
  "Like `create-image', but with set DATA-P and TYPE arguments."
  (declare (indent 1) (debug t))
  (let ((image-data (make-symbol "data")))
    `(let ((,image-data ,data))
       (apply #'create-image ,image-data (pdf-view-image-type) t ,@props
              (cl-list*
               :relief (or pdf-view-image-relief 0)
               (when (and (eq (framep-on-display) 'mac)
                          (= (pdf-util-frame-scale-factor) 2))
                 (list :data-2x ,image-data)))))))

(defun pdf-view-create-page (page &optional window)
  "Create an image of PAGE for display on WINDOW."
  (let* ((size (pdf-view-desired-image-size page window))
         (data (pdf-cache-renderpage
                page (car size)
                (if pdf-view-use-scaling
                    (* 2 (car size))
                  (car size))))
         (hotspots (pdf-view-apply-hotspot-functions
                    window page size)))
    (pdf-view-create-image data
      :width (car size)
      :rotation (or pdf-view--current-rotation 0)
      :map hotspots
      :pointer 'arrow)))

(defun pdf-view-image-size (&optional displayed-p window page)
  "Return the size in pixel of the current image in WINDOW.

If DISPLAYED-P is non-nil, return the size of the displayed
image.  These values may be different, if slicing is used.

If PAGE is non-nil return its size instead of current page."
  (let ((display-prop (if pdf-view-roll-minor-mode
                          (progn (setq window (if (windowp window) window (selected-window)))
                                 (setq page (or page (pdf-view-current-page window)))
                                 (unless (memq page (image-mode-window-get 'displayed-pages window))
                                   (pdf-view-display-page page window))
                                 (overlay-get (pdf-roll-page-overlay page window) 'display))
                        (image-get-display-property))))
    (if displayed-p
        (image-display-size display-prop t)
      (image-size display-prop t))))

(defun pdf-view-image-offset (&optional window)
  ;; TODO: add WINDOW to docstring.
  "Return the offset of the current image.

It is equal to \(LEFT . TOP\) of the current slice in pixel."
  (let* ((slice (pdf-view-current-slice window)))
    (cond
     (slice
      (pdf-util-scale-relative-to-pixel
       (cons (nth 0 slice) (nth 1 slice))
       window))
     (t
      (cons 0 0)))))

(defun pdf-view-display-page (page &optional window)
  "Display page PAGE in WINDOW."
  (setf (pdf-view-window-needs-redisplay window) nil)
  (pdf-view-display-image
   (pdf-view-create-page page window) page window))

(defun pdf-view-display-image (image page &optional window inhibit-slice-p)
  ;; TODO: write documentation!
  (if pdf-view-roll-minor-mode
      (pdf-roll-display-image
       image page (or window (selected-window)) inhibit-slice-p)
    (let ((ol (pdf-view-current-overlay window)))
      (when (window-live-p (overlay-get ol 'window))
        (let* ((size (image-size image t))
               (slice (if (not inhibit-slice-p)
                          (pdf-view-current-slice window)))
               (displayed-width (floor
                                 (if slice
                                     (* (nth 2 slice)
                                        (car (image-size image)))
                                   (car (image-size image))))))
          (setf (pdf-view-current-image window) image)
          (move-overlay ol (point-min) (point-max))
          ;; In case the window is wider than the image, center the image
          ;; horizontally.
          (overlay-put ol 'before-string
                       (when (> (window-width window)
                                displayed-width)
                         (propertize " " 'display
                                     `(space :align-to
                                       ,(/ (- (window-width window)
                                              displayed-width) 2)))))
          (overlay-put ol 'display
                       (if slice
                           (list (cons 'slice
                                       (pdf-util-scale slice size 'round))
                                 image)
                         image))
          (let* ((win (overlay-get ol 'window))
                 (hscroll (image-mode-window-get 'hscroll win))
                 (vscroll (image-mode-window-get 'vscroll win)))
            ;; Reset scroll settings, in case they were changed.
            (if hscroll (set-window-hscroll win hscroll))
            (if vscroll (set-window-vscroll
                         win vscroll pdf-view-have-image-mode-pixel-vscroll))))))))

(defun pdf-view--redisplay (&optional window)
  "Redisplay page in WINDOW.

If WINDOW is t, redisplay pages in all windows."
  (unless pdf-view-inhibit-redisplay
    (if (not (eq t window))
        (pdf-view-display-page
         (pdf-view-current-page window)
         window)
      (dolist (win (get-buffer-window-list nil nil t))
        (pdf-view-display-page
         (pdf-view-current-page win)
         win))
      (when (consp image-mode-winprops-alist)
        (dolist (window (mapcar #'car image-mode-winprops-alist))
          (unless (or (not (window-live-p window))
                      (eq (current-buffer)
                          (window-buffer window)))
            (setf (pdf-view-window-needs-redisplay window) t)))))
    (force-mode-line-update)))

(defun pdf-view-redisplay (&optional window)
  (if pdf-view-roll-minor-mode
      (pdf-roll-redisplay window)
    (pdf-view--redisplay window)))

(defun pdf-view-redisplay-pages (&rest pages)
  "Redisplay PAGES in all windows."
  (pdf-util-assert-pdf-buffer)
  (dolist (window (get-buffer-window-list nil nil t))
    (when (cl-some (lambda (page) (memq page pages))
                   (or (image-mode-window-get 'displayed-pages window)
                       (list (pdf-view-current-page window))))
      (pdf-view-redisplay window))))

(defun pdf-view-maybe-redisplay-resized-windows ()
  "Redisplay some windows needing redisplay."
  (unless (or (numberp pdf-view-display-size)
              (pdf-view-active-region-p)
              (> (minibuffer-depth) 0))
    (dolist (window (get-buffer-window-list nil nil t))
      (let ((stored (pdf-view-current-window-size window))
            (size (cons (window-width window)
                        (window-height window))))
        (unless (equal size stored)
          (setf (pdf-view-current-window-size window) size)
          (unless (or (null stored)
                      (and (eq pdf-view-display-size 'fit-width)
                           (eq (car size) (car stored)))
                      (and (eq pdf-view-display-size 'fit-height)
                           (eq (cdr size) (cdr stored))))
            (pdf-view-redisplay window)))))))

(defun pdf-view-redisplay-some-windows ()
  (pdf-view-maybe-redisplay-resized-windows)
  (dolist (window (get-buffer-window-list nil nil t))
    (when (pdf-view-window-needs-redisplay window)
      (pdf-view-redisplay window))))

(defun pdf-view-new-window-function (winprops)
  ;; TODO: write documentation!
  ;; (message "New window %s for buf %s" (car winprops) (current-buffer))
  (cl-assert (or (eq t (car winprops))
                 (eq (window-buffer (car winprops)) (current-buffer))))
  (let ((ol (image-mode-window-get 'overlay winprops)))
    (if ol
        (progn
          (setq ol (copy-overlay ol))
          ;; `ol' might actually be dead.
          (move-overlay ol (point-min) (point-max)))
      (setq ol (make-overlay (point-min) (point-max) nil t))
      (overlay-put ol 'pdf-view t))
    (overlay-put ol 'window (car winprops))
    (unless (windowp (car winprops))
      ;; It's a pseudo entry.  Let's make sure it's not displayed (the
      ;; `window' property is only effective if its value is a window).
      (cl-assert (eq t (car winprops)))
      (delete-overlay ol))
    (image-mode-window-put 'overlay ol)
    ;; Clean up some overlays.
    (dolist (ov (overlays-in (point-min) (point-max)))
      (when (and (windowp (overlay-get ov 'window))
                 (not (window-live-p (overlay-get ov 'window))))
        (delete-overlay ov)))
    (when (and (windowp (car winprops))
               (null (image-mode-window-get 'image winprops)))
      ;; We're not displaying an image yet, so let's do so.  This
      ;; happens when the buffer is displayed for the first time.
      (with-selected-window (car winprops)
        (pdf-view-goto-page
         (or (image-mode-window-get 'page t) 1))))))

(defun pdf-view-desired-image-size (&optional page window)
  ;; TODO: write documentation!
  (let* ((pagesize (pdf-cache-pagesize
                    (or page (pdf-view-current-page window))))
         (slice (pdf-view-current-slice window))
         (width-scale (/ (/ (float (window-body-width window t))
                            (or (nth 2 slice) 1.0))
                         (float (car pagesize))))
         (height (- (nth 3 (window-inside-pixel-edges window))
                    (nth 1 (window-inside-pixel-edges window))
                    1))
         (height-scale (/ (/ (float height)
                             (or (nth 3 slice) 1.0))
                          (float (cdr pagesize))))
         (scale width-scale))
    (if (numberp pdf-view-display-size)
        (setq scale (float pdf-view-display-size))
      (cl-case pdf-view-display-size
        (fit-page
         (setq scale (min height-scale width-scale)))
        (fit-height
         (setq scale height-scale))
        (t
         (setq scale width-scale))))
    (let ((width (floor (* (car pagesize) scale)))
          (height (floor (* (cdr pagesize) scale))))
      (when (> width (max 1 (or pdf-view-max-image-width width)))
        (setq width pdf-view-max-image-width
              height (* height (/ (float pdf-view-max-image-width) width))))
      (cons (max 1 width) (max 1 height)))))

(defun pdf-view-text-regions-hotspots-function (page size)
  "Return a list of hotspots for text regions on PAGE using SIZE.

This will display a text cursor, when hovering over them."
  (local-set-key [pdf-view-text-region t]
                 'pdf-util-image-map-mouse-event-proxy)
  (mapcar (lambda (region)
            (let ((e (pdf-util-scale region size 'round)))
              `((rect . ((,(nth 0 e) . ,(nth 1 e))
                         . (,(nth 2 e) . ,(nth 3 e))))
                pdf-view-text-region
                (pointer text))))
          (pdf-cache-textregions page)))

(define-minor-mode pdf-view-dark-minor-mode
  "Mode for PDF documents with dark background.

This tells the various modes to use their face's dark colors."
  :group 'pdf-view
  (pdf-util-assert-pdf-buffer)
  ;; FIXME: This should really be run in a hook.
  (when (bound-and-true-p pdf-isearch-active-mode)
    (with-no-warnings
      (pdf-isearch-redisplay)
      (pdf-isearch-message
       (if pdf-view-dark-minor-mode "dark mode" "light mode")))))

(define-minor-mode pdf-view-printer-minor-mode
  "Display the PDF as it would be printed."
  :group 'pdf-view
  :lighter " Prn"
  (pdf-util-assert-pdf-buffer)
  (let ((enable (lambda ()
                  (pdf-info-setoptions :render/printed t))))
    (cond
     (pdf-view-printer-minor-mode
      (add-hook 'after-save-hook enable nil t)
      (add-hook 'after-revert-hook enable nil t))
     (t
      (remove-hook 'after-save-hook enable t)
      (remove-hook 'after-revert-hook enable t))))
  (pdf-info-setoptions :render/printed pdf-view-printer-minor-mode)
  (pdf-cache-clear-images)
  (pdf-view-redisplay t))

(define-minor-mode pdf-view-midnight-minor-mode
  "Apply a color-filter appropriate for past midnight reading.

The colors are determined by the variable
`pdf-view-midnight-colors', which see. "
  :group 'pdf-view
  :lighter " Mid"
  (pdf-util-assert-pdf-buffer)
  ;; FIXME: Maybe these options should be passed stateless to pdf-info-renderpage ?
  (let ((enable (lambda ()
                  (pdf-info-setoptions
                   :render/foreground (or (car pdf-view-midnight-colors) "black")
                   :render/background (or (cdr pdf-view-midnight-colors) "white")
                   :render/gamma pdf-view-midnight-gamma
                   :render/gammabeforeinvert pdf-view-midnight-gamma-before-invert
                   :render/usecolors
                   (if pdf-view-midnight-invert
                       ;; If midnight invert is enabled, pass "2" indicating
                       ;; that :render/foreground and :render/background should
                       ;; be ignored and to instead invert the PDF (preserving
                       ;; hue)
                       2
                     ;; If invert is not enabled, pass "1" indictating that
                     ;; :render/foreground and :render/background should be used
                     1)))))
    (cond
     (pdf-view-midnight-minor-mode
      (add-hook 'after-save-hook enable nil t)
      (add-hook 'after-revert-hook enable nil t)
      (funcall enable))
     (t
      (remove-hook 'after-save-hook enable t)
      (remove-hook 'after-revert-hook enable t)
      (pdf-info-setoptions
       ;; Value "0" indicates that colors should remain unchanged
       :render/usecolors 0))))
  (pdf-cache-clear-images)
  (pdf-view-redisplay t))

(defun pdf-view-set-theme-background ()
  "Set the buffer's color filter to correspond to the current Emacs theme."
  (pdf-util-assert-pdf-buffer)
  (pdf-info-setoptions
   :render/foreground (face-foreground 'default nil)
   :render/background (face-background 'default nil)
   :render/usecolors 1))

(defun pdf-view-refresh-themed-buffer (&optional get-theme)
  "Refresh the current buffer to activate applied colors.

When GET-THEME is non-nil, also reset the applied colors to the
current theme's colors."
  (pdf-util-assert-pdf-buffer)
  (pdf-cache-clear-images)
  (when get-theme
    (pdf-view-set-theme-background))
  (pdf-view-redisplay t))

(define-minor-mode pdf-view-themed-minor-mode
  "Synchronize color filter with the present Emacs theme.

The colors are determined by the `face-foreground' and
`face-background' of the currently active theme."
  :group 'pdf-view
  :lighter " Thm"
  (pdf-util-assert-pdf-buffer)
  (cond
   (pdf-view-themed-minor-mode
    (add-hook 'after-save-hook #'pdf-view-set-theme-background nil t)
    (add-hook 'after-revert-hook #'pdf-view-set-theme-background nil t))
   (t
    (remove-hook 'after-save-hook #'pdf-view-set-theme-background t)
    (remove-hook 'after-revert-hook #'pdf-view-set-theme-background t)
    (pdf-info-setoptions :render/usecolors 0)))
  (pdf-view-refresh-themed-buffer pdf-view-themed-minor-mode))

(when pdf-view-use-unicode-ligther
  ;; This check uses an implementation detail, which hopefully gets the
  ;; right answer.
  (and (fontp (char-displayable-p ?⎙))
       (setcdr (assq 'pdf-view-printer-minor-mode minor-mode-alist)
               (list " ⎙" )))
  (and (fontp (char-displayable-p ?🌙))
       (setcdr (assq 'pdf-view-midnight-minor-mode minor-mode-alist)
               (list  " 🌙" ))))


;; * ================================================================== *
;; * Hotspot handling
;; * ================================================================== *

(defun pdf-view-add-hotspot-function (fn &optional layer)
  "Register FN as a hotspot function in the current buffer, using LAYER.

FN will be called in the PDF buffer with the page-number and the
image size \(WIDTH . HEIGHT\) as arguments.  It should return a
list of hotspots applicable to the the :map image-property.

LAYER determines the order: Functions in a higher LAYER will
supersede hotspots in lower ones."
  (push (cons (or layer 0) fn)
        pdf-view--hotspot-functions))

(defun pdf-view-remove-hotspot-function (fn)
  "Unregister FN as a hotspot function in the current buffer."
  (setq pdf-view--hotspot-functions
        (cl-remove fn pdf-view--hotspot-functions
                   :key 'cdr)))

(defun pdf-view-sorted-hotspot-functions ()
  ;; TODO: write documentation!
  (mapcar 'cdr (cl-sort (copy-sequence pdf-view--hotspot-functions)
                        '> :key 'car)))

(defun pdf-view-apply-hotspot-functions (window page image-size)
  ;; TODO: write documentation!
  (unless pdf-view-inhibit-hotspots
    (save-selected-window
      (when window (select-window window 'norecord))
      (apply 'nconc
             (mapcar (lambda (fn)
                       (funcall fn page image-size))
                     (pdf-view-sorted-hotspot-functions))))))


;; * ================================================================== *
;; * Region
;; * ================================================================== *

(defun pdf-view--push-mark ()
  ;; TODO: write documentation!
  (let (mark-ring)
    (push-mark-command nil))
  (setq deactivate-mark nil))

(defun pdf-view-active-region (&optional deactivate-p)
  "Return the active region, as a cons cell of page number and list of edges.

Deactivate the region if DEACTIVATE-P is non-nil."
  (pdf-view-assert-active-region)
  (prog1
      pdf-view-active-region
    (when deactivate-p
      (pdf-view-deactivate-region))))

(defun pdf-view-deactivate-region ()
  "Deactivate the region."
  (interactive)
  (when pdf-view-active-region
    (setq pdf-view-active-region nil)
    (deactivate-mark)
    (pdf-view-redisplay t)))

(defun pdf-view-mouse-set-region (event &optional allow-extend-p
                                        rectangle-p
                                        selection-style)
  "Select a region of text using the mouse with mouse event EVENT.

Allow for stacking of regions, if ALLOW-EXTEND-P is non-nil.

Create a rectangular region, if RECTANGLE-P is non-nil.

Overwrite `pdf-view-selection-style' with SELECTION-STYLE,
which is one of `glyph', `word', or `line'.

Stores the region in `pdf-view-active-region'."
  (interactive "@e")
  (setq pdf-view--have-rectangle-region rectangle-p)
  (unless (and (eventp event)
               (mouse-event-p event))
    (signal 'wrong-type-argument (list 'mouse-event-p event)))
  (unless (and allow-extend-p
               (or (null (get this-command 'pdf-view-region-window))
                   (equal (get this-command 'pdf-view-region-window)
                          (selected-window))))
    (pdf-view-deactivate-region))
  (put this-command 'pdf-view-region-window
       (selected-window))
  (let* ((window (selected-window))
         (pos (event-start event))
         (begin-inside-image-p t)
         (begin (if (posn-image pos)
                    (posn-object-x-y pos)
                  (setq begin-inside-image-p nil)
                  (posn-x-y pos)))
         (abs-begin (posn-x-y pos))
         (page (if pdf-view-roll-minor-mode
                   (/ (+ 3 (posn-point pos)) 4)
                 (pdf-view-current-page)))
         (margin (frame-char-height))
         (selection-style (or selection-style pdf-view-selection-style))
         pdf-view-continuous
         region)
    (setq pdf-view-active-region (list page))
    (when (pdf-util-track-mouse-dragging (event 0.05)
            (let* ((pos (event-start event))
                   (end (posn-object-x-y pos))
                   (end-inside-image-p
                    (and (eq window (posn-window pos))
                         (posn-image pos))))
              (when (or end-inside-image-p
                        begin-inside-image-p)
                (cond
                 ((and end-inside-image-p
                       (not begin-inside-image-p))
                  ;; Started selection outside the image, setup begin.
                  (let* ((xy (posn-x-y pos))
                         (dxy (cons (- (car xy) (car begin))
                                    (- (cdr xy) (cdr begin))))
                         (size (pdf-view-image-size t)))
                    (setq begin (cons (max 0 (min (car size)
                                                  (- (car end) (car dxy))))
                                      (max 0 (min (cdr size)
                                                  (- (cdr end) (cdr dxy)))))
                          ;; Store absolute position for later.
                          abs-begin (cons (- (car xy)
                                             (- (car end)
                                                (car begin)))
                                          (- (cdr xy)
                                             (- (cdr end)
                                                (cdr begin))))
                          begin-inside-image-p t)))
                 ((and begin-inside-image-p
                       (not end-inside-image-p))
                  ;; Moved outside the image, setup end.
                  (let* ((xy (posn-x-y pos))
                         (dxy (cons (- (car xy) (car abs-begin))
                                    (- (cdr xy) (cdr abs-begin))))
                         (size (pdf-view-image-size t)))
                    (setq end (cons (max 0 (min (car size)
                                                (+ (car begin) (car dxy))))
                                    (max 0 (min (cdr size)
                                                (+ (cdr begin) (cdr dxy)))))))))
                (let* ((iregion (if rectangle-p
                                    (list (min (car begin) (car end))
                                          (min (cdr begin) (cdr end))
                                          (max (car begin) (car end))
                                          (max (cdr begin) (cdr end)))
                                  (list (car begin) (cdr begin)
                                        (car end) (cdr end))))
                       (y (cdr (posn-x-y pos)))
                       (dy (- y (cdr abs-begin))))
                  (setq region
                        (pdf-util-scale-pixel-to-relative iregion))
                  (pdf-view-display-region
                   (cons page (cons region (cdr pdf-view-active-region)))
                   rectangle-p
                   selection-style)
                  (if pdf-view-roll-minor-mode
                      (cond
                       ((and (> dy 0) (< (- (window-text-height window t) y) margin))
                        (pdf-roll-scroll-forward
                         (min margin
                              (or (nth 3 (pos-visible-in-window-p (posn-point pos) window t)) 0))))
                       ((and (< dy 0) (< (- y (window-header-line-height window)) margin))
                        (pdf-roll-scroll-backward
                         (min margin
                              (or (nth 2 (pos-visible-in-window-p (posn-point pos) window t)) 0)))))
                    (pdf-util-scroll-to-edges iregion))))))
      (cl-callf append (cdr pdf-view-active-region) (list region))
      (pdf-view--push-mark))))

(defun pdf-view-mouse-extend-region (event)
  "Extend the currently active region with mouse event EVENT."
  (interactive "@e")
  (pdf-view-mouse-set-region
   event t pdf-view--have-rectangle-region))

(defun pdf-view-mouse-set-region-rectangle (event)
  "Like `pdf-view-mouse-set-region' but displays as a rectangle.

EVENT is the mouse event.

This is more useful for commands like
`pdf-view-extract-region-image'."
  (interactive "@e")
  (pdf-view-mouse-set-region event nil t))

(defun pdf-view-display-region (&optional region rectangle-p selection-style)
  ;; TODO: write documentation!
  (unless region
    (pdf-view-assert-active-region)
    (setq region pdf-view-active-region))
  (let ((colors (pdf-util-face-colors
                 (if rectangle-p 'pdf-view-rectangle 'pdf-view-region)
                 (bound-and-true-p pdf-view-dark-minor-mode)))
        (page (car region))
        (width (car (pdf-view-image-size))))
    (pdf-view-display-image
     (pdf-view-create-image
         (if rectangle-p
             (pdf-info-renderpage-highlight
              page width nil
              `(,(car colors) ,(cdr colors) 0.35 ,@(cdr region)))
           (pdf-info-renderpage-text-regions
            page width nil selection-style nil
            `(,(car colors) ,(cdr colors) ,@(cdr region))))
       :width width)
     (when pdf-view-roll-minor-mode page))))

(defun pdf-view-kill-ring-save ()
  "Copy the region to the `kill-ring'."
  (interactive)
  (pdf-view-assert-active-region)
  (let* ((txt (pdf-view-active-region-text)))
    (pdf-view-deactivate-region)
    (kill-new (mapconcat 'identity txt "\n"))))

(defun pdf-view-mark-whole-page ()
  "Mark the whole page."
  (interactive)
  (pdf-view-deactivate-region)
  (setq pdf-view-active-region
        (cons (pdf-view-current-page) (list (list 0 0 1 1))))
  (pdf-view--push-mark)
  (pdf-view-display-region))

(defun pdf-view-active-region-text ()
  "Return the text of the active region as a list of strings."
  (pdf-view-assert-active-region)
  (mapcar
   (lambda (edges)
     (pdf-info-gettext
      (car pdf-view-active-region)
      edges
      pdf-view-selection-style))
   (cdr pdf-view-active-region)))

(defun pdf-view-extract-region-image (regions &optional page size
                                              output-buffer no-display-p)
  ;; TODO: what is "resp."? Avoid contractions.
  "Create a PNG image of REGIONS.

REGIONS should have the same form as `pdf-view-active-region',
which see.  PAGE and SIZE are the page resp. base-size of the
image from which the image-regions will be created; they default
to `pdf-view-current-page' resp. `pdf-view-image-size'.

Put the image in OUTPUT-BUFFER, defaulting to \"*PDF region
image*\" and display it, unless NO-DISPLAY-P is non-nil.

In case of multiple regions, the resulting image is constructed
by joining them horizontally.  For this operation (and this only)
the `convert' program is used."

  (interactive
   (list (if (pdf-view-active-region-p)
             (pdf-view-active-region t)
           '(,(pdf-view-current-page) (0 0 1 1)))))
  (unless page
    (setq page (car regions)))
  (unless size
    (setq size (pdf-view-image-size nil nil page)))
  (unless output-buffer
    (setq output-buffer (get-buffer-create "*PDF image*")))
  (let* ((images (mapcar (lambda (edges)
                           (let ((file (make-temp-file "pdf-view"))
                                 (coding-system-for-write 'binary))
                             (write-region
                              (pdf-info-renderpage
                               page (car size)
                               :crop-to edges)
                              nil file nil 'no-message)
                             file))
                         (cdr regions)))
         result)
    (unwind-protect
        (progn
          (if (= (length images) 1)
              (setq result (car images))
            (setq result (make-temp-file "pdf-view"))
            ;; Join the images horizontally with a gap of 10 pixel.
            (pdf-util-convert
             "-noop" ;; workaround limitations of this function
             result
             :commands `("("
                         ,@images
                         "-background" "white"
                         "-splice" "0x10+0+0"
                         ")"
                         "-gravity" "Center"
                         "-append"
                         "+gravity"
                         "-chop" "0x10+0+0")
             :apply '((0 0 0 0))))
          (with-current-buffer output-buffer
            (let ((inhibit-read-only t))
              (erase-buffer))
            (set-buffer-multibyte nil)
            (insert-file-contents-literally result)
            (image-mode)
            (unless no-display-p
              (pop-to-buffer (current-buffer)))))
      (dolist (f (cons result images))
        (when (file-exists-p f)
          (delete-file f))))))

(defun pdf-view-set-selection-style (&optional style)
  "Set `pdf-view-selection-style' to STYLE in the current buffer.

When called interactively or without an argument, cycle between
the selection styles."
  (interactive)
  (unless style
    (setq style (or (cadr (memq pdf-view-selection-style '(glyph word line)))
                    'glyph))
    (message "Setting selection style to `%s'." style))
  (pdf-view-deactivate-region)
  (setq-local pdf-view-selection-style style))


;; * ================================================================== *
;; * Bookmark + Register Integration
;; * ================================================================== *

(defvar pdf-view--bookmark-to-restore nil
  "Used to hold a bookmark that is still to be restored.")
(defun pdf-view-bookmark-make-record  (&optional no-page no-slice no-size no-origin)
  ;; TODO: add NO-PAGE, NO-SLICE, NO-SIZE, NO-ORIGIN to the docstring.
  "Create a bookmark PDF record.

The optional, boolean args exclude certain attributes."
  (or pdf-view--bookmark-to-restore
      (let ((win (car (cl-find-if #'window-live-p image-mode-winprops-alist
                                  :key #'car-safe))))
        (cons (buffer-name)
              (append (bookmark-make-record-default
                       nil t (if pdf-view-roll-minor-mode (point) 1))
                      `(,(unless no-page
                           (cons 'page (pdf-view-current-page win)))
                        ,(unless no-slice
                           (cons 'slice (and win (pdf-view-current-slice win))))
                        ,(unless no-size
                           (cons 'size pdf-view-display-size))
                        ,(unless no-origin
                           (cons 'origin
                                 (and win
                                      (let* ((edges (pdf-util-image-displayed-edges
                                                     win (eq (window-buffer win) (current-buffer)))))
                                        (pdf-util-scale-pixel-to-relative
                                         (cons (car edges) (cadr edges)) nil
                                         (eq (current-buffer) (window-buffer)) win)))))
                        (handler . pdf-view-bookmark-jump-handler)))))))

;;;###autoload
(defun pdf-view-bookmark-jump-handler (bmk)
  "The bookmark handler-function interface for bookmark BMK.

See also `pdf-view-bookmark-make-record'."
  (let* ((file (bookmark-prop-get bmk 'filename))
         (buf (or (find-buffer-visiting file)
                  (find-file-noselect file)))
         (buf-chg-fns-p (boundp 'window-buffer-change-functions))
         (hook (if (and buf-chg-fns-p (not (get-buffer-window buf)))
                   'window-buffer-change-functions
                 'bookmark-after-jump-hook))
         (show-fn-sym (make-symbol "pdf-show-buffer-function")))
    (fset show-fn-sym
          (lambda (&optional win)
            (when (eq buf (current-buffer))
              (with-selected-window
                  (or win
                      (get-buffer-window buf 0)
                      (selected-window))
                (remove-hook hook show-fn-sym buf-chg-fns-p)
                (unless (derived-mode-p 'pdf-view-mode)
                  (pdf-view-mode))
                (when-let ((size (bookmark-prop-get
                                  pdf-view--bookmark-to-restore 'size)))
                  (setq-local pdf-view-display-size size))
                (when-let ((slice (bookmark-prop-get
                                   pdf-view--bookmark-to-restore 'slice)))
                  (apply 'pdf-view-set-slice slice))
                (when-let ((page (bookmark-prop-get
                                  pdf-view--bookmark-to-restore 'page))
                           ((numberp page)))
                  (pdf-view-goto-page page win))
                (when-let ((origin (bookmark-prop-get
                                    pdf-view--bookmark-to-restore 'origin))
                           (size (pdf-view-image-size t win)))
                  (image-set-window-hscroll
                   (round (/ (* (car origin) (car size))
                             (frame-char-width))))
                  (image-set-window-vscroll
                   (round (/ (* (cdr origin) (cdr size))
                             (if pdf-view-have-image-mode-pixel-vscroll
                                 1
                               (frame-char-height))))))
                (setq-local pdf-view--bookmark-to-restore nil)))))
    (set-buffer buf)
    (setq-local pdf-view--bookmark-to-restore bmk)
    (add-hook hook show-fn-sym nil buf-chg-fns-p)))

(defun pdf-view-bookmark-jump (bmk)
  "Switch to bookmark BMK.

This function is like `bookmark-jump', but it always uses the
selected window for display and does not run any hooks.  Also, it
works only with bookmarks created by
`pdf-view-bookmark-make-record'."

  (let* ((file (bookmark-prop-get bmk 'filename))
         (buffer (or (find-buffer-visiting file)
                     (find-file-noselect file))))
    (switch-to-buffer buffer)
    (let (bookmark-after-jump-hook)
      (pdf-view-bookmark-jump-handler bmk)
      (run-hooks 'bookmark-after-jump-hook))))

;; Register support using cl-defstruct (replaces obsolete registerv-make)
(cl-defstruct (pdf-view-register
               (:constructor nil)
               (:constructor pdf-view-register--make (bookmark))
               (:copier nil))
  "A PDF position register entry."
  bookmark)

(cl-defmethod register-val-jump-to ((val pdf-view-register) _arg)
  "Jump to the PDF position stored in VAL."
  (pdf-view-bookmark-jump (pdf-view-register-bookmark val)))

(cl-defmethod register-val-describe ((val pdf-view-register) _verbose)
  "Print a description of the PDF position stored in VAL."
  (let* ((bmk (pdf-view-register-bookmark val))
         (file (bookmark-prop-get bmk 'filename))
         (buffer (find-buffer-visiting file))
         (page (bookmark-prop-get bmk 'page))
         (origin (bookmark-prop-get bmk 'origin)))
    (princ (format "PDF position: %s, page %d, %d%%"
                   (if buffer
                       (buffer-name buffer)
                     file)
                   (or page 1)
                   (if origin
                       (round (* 100 (cdr origin)))
                     0)))))

(cl-defmethod register-val-insert ((val pdf-view-register))
  "Insert the PDF bookmark stored in VAL."
  (insert (format "%S" (pdf-view-register-bookmark val))))

(defun pdf-view-registerv-make ()
  "Create a PDF register entry of the current position."
  (pdf-view-register--make
   (pdf-view-bookmark-make-record nil t t)))

(defmacro pdf-view-with-register-alist (&rest body)
  "Setup the proper binding for `register-alist' in BODY.

This macro may not work as desired when it is nested.  See also
`pdf-view-use-dedicated-register'."
  (declare (debug t) (indent 0))
  (let ((dedicated-p (make-symbol "dedicated-p")))
    `(let* ((,dedicated-p pdf-view-use-dedicated-register)
            (register-alist
             (if ,dedicated-p
                 pdf-view-register-alist
               register-alist)))
       (unwind-protect
           (progn ,@body)
         (when ,dedicated-p
           (setq pdf-view-register-alist register-alist))))))

(defun pdf-view-position-to-register (register)
  "Store current PDF position in register REGISTER.

See also `point-to-register'."
  (interactive
   (list (pdf-view-with-register-alist
           (register-read-with-preview "Position to register: "))))
  (pdf-view-with-register-alist
    (set-register register (pdf-view-registerv-make))))

(defun pdf-view-jump-to-register (register &optional delete return-register)
  ;; TODO: add RETURN-REGISTER to the docstring.
  "Move point to a position stored in a REGISTER.

Optional parameter DELETE is defined as in `jump-to-register'."
  (interactive
   (pdf-view-with-register-alist
     (list
      (register-read-with-preview "Jump to register: ")
      current-prefix-arg
      (and (or pdf-view-use-dedicated-register
               (local-variable-p 'register-alist))
           (characterp last-command-event)
           last-command-event))))
  (pdf-view-with-register-alist
    (let ((return-pos (and return-register
                           (pdf-view-registerv-make))))
      (jump-to-register register delete)
      (when return-register
        (set-register return-register return-pos)))))

(provide 'pdf-view)

;;; pdf-view.el ends here
