dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

pdf-view.el (64333B)


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