Klimi's new dotfiles with stow.
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

1676 lignes
62 KiB

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