2023-02-21 18:17:08 +00:00
|
|
|
|
;;; pdf-view.el --- View PDF documents. -*- lexical-binding:t -*-
|
|
|
|
|
|
|
|
|
|
;; Copyright (C) 2013 Andreas Politz
|
|
|
|
|
|
|
|
|
|
;; Author: Andreas Politz <politza@fh-trier.de>
|
|
|
|
|
;; Keywords: files, doc-view, pdf
|
|
|
|
|
|
|
|
|
|
;; This program is free software; you can redistribute it and/or modify
|
|
|
|
|
;; it under the terms of the GNU General Public License as published by
|
|
|
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
;; (at your option) any later version.
|
|
|
|
|
|
|
|
|
|
;; This program is distributed in the hope that it will be useful,
|
|
|
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
;; GNU General Public License for more details.
|
|
|
|
|
|
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
|
|
|
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
|
|
|
|
|
|
;; Functions related to viewing PDF documents.
|
|
|
|
|
|
|
|
|
|
;;; Code:
|
|
|
|
|
|
|
|
|
|
(require 'image-mode)
|
|
|
|
|
(require 'pdf-macs)
|
|
|
|
|
(require 'pdf-util)
|
|
|
|
|
(require 'pdf-info)
|
|
|
|
|
(require 'pdf-cache)
|
|
|
|
|
(require 'jka-compr)
|
|
|
|
|
(require 'bookmark)
|
|
|
|
|
(require 'password-cache)
|
|
|
|
|
|
|
|
|
|
(declare-function cua-copy-region "cua-base")
|
|
|
|
|
(declare-function pdf-tools-pdf-buffer-p "pdf-tools")
|
|
|
|
|
|
|
|
|
|
;; * ================================================================== *
|
|
|
|
|
;; * Customizations
|
|
|
|
|
;; * ================================================================== *
|
|
|
|
|
|
|
|
|
|
(defgroup pdf-view nil
|
|
|
|
|
"View PDF documents."
|
|
|
|
|
:group 'pdf-tools)
|
|
|
|
|
|
|
|
|
|
(defcustom pdf-view-display-size 'fit-width
|
|
|
|
|
"The desired size of displayed pages.
|
|
|
|
|
|
|
|
|
|
This may be one of `fit-height', `fit-width', `fit-page' or a
|
|
|
|
|
number as a scale factor applied to the document's size. Any
|
|
|
|
|
other value behaves like `fit-width'."
|
|
|
|
|
:group 'pdf-view
|
|
|
|
|
:type '(choice number
|
|
|
|
|
(const fit-height)
|
|
|
|
|
(const fit-width)
|
|
|
|
|
(const fit-page)))
|
|
|
|
|
|
|
|
|
|
(make-variable-buffer-local 'pdf-view-display-size)
|
|
|
|
|
|
|
|
|
|
(defcustom pdf-view-resize-factor 1.25
|
|
|
|
|
"Fractional amount of resizing of one resize command."
|
|
|
|
|
:group 'pdf-view
|
|
|
|
|
:type 'number)
|
|
|
|
|
|
|
|
|
|
(defcustom pdf-view-continuous t
|
|
|
|
|
"In Continuous mode reaching the page edge advances to next/previous page.
|
|
|
|
|
|
|
|
|
|
When non-nil, scrolling a line upward at the bottom edge of the page
|
|
|
|
|
moves to the next page, and scrolling a line downward at the top edge
|
|
|
|
|
of the page moves to the previous page."
|
|
|
|
|
:type 'boolean
|
|
|
|
|
:group 'pdf-view)
|
|
|
|
|
|
|
|
|
|
(defcustom pdf-view-bounding-box-margin 0.05
|
|
|
|
|
"Fractional margin used for slicing with the bounding-box."
|
|
|
|
|
:group 'pdf-view
|
|
|
|
|
:type 'number)
|
|
|
|
|
|
|
|
|
|
(defcustom pdf-view-use-imagemagick nil
|
|
|
|
|
"Whether imagemagick should be used for rendering.
|
|
|
|
|
|
|
|
|
|
This variable has no effect, if imagemagick was not compiled into
|
|
|
|
|
Emacs or if imagemagick is the only way to display PNG images.
|
|
|
|
|
FIXME: Explain dis-/advantages of imagemagick and png."
|
|
|
|
|
:group 'pdf-view
|
|
|
|
|
:type 'boolean)
|
|
|
|
|
|
|
|
|
|
(defcustom pdf-view-use-scaling t
|
|
|
|
|
"Whether images should be allowed to be scaled for rendering.
|
|
|
|
|
|
|
|
|
|
This variable affects both the reuse of higher-resolution images
|
|
|
|
|
as lower-resolution ones by down-scaling the image. As well as
|
|
|
|
|
the rendering of higher-resolution for high-resolution displays,
|
|
|
|
|
if available."
|
|
|
|
|
:group 'pdf-view
|
|
|
|
|
:type 'boolean)
|
|
|
|
|
|
|
|
|
|
(defface pdf-view-region
|
|
|
|
|
'((((background dark)) (:inherit region))
|
|
|
|
|
(((background light)) (:inherit region)))
|
|
|
|
|
"Face used to determine the colors of the region."
|
|
|
|
|
:group 'pdf-view
|
|
|
|
|
:group 'pdf-tools-faces)
|
|
|
|
|
|
|
|
|
|
(defface pdf-view-rectangle
|
|
|
|
|
'((((background dark)) (:inherit highlight))
|
|
|
|
|
(((background light)) (:inherit highlight)))
|
|
|
|
|
"Face used to determine the colors of the highlighted rectangle."
|
|
|
|
|
:group 'pdf-view
|
|
|
|
|
:group 'pdf-tools-faces)
|
|
|
|
|
|
|
|
|
|
(defcustom pdf-view-midnight-colors '("#839496" . "#002b36" )
|
|
|
|
|
"Colors used when command `pdf-view-midnight-minor-mode' is activated.
|
|
|
|
|
|
|
|
|
|
This should be a cons \(FOREGROUND . BACKGROUND\) of colors."
|
|
|
|
|
:group 'pdf-view
|
|
|
|
|
:type '(cons (color :tag "Foreground")
|
|
|
|
|
(color :tag "Background")))
|
|
|
|
|
|
|
|
|
|
(defcustom pdf-view-midnight-invert t
|
|
|
|
|
"In midnight mode invert the image color lightness maintaining hue.
|
|
|
|
|
|
|
|
|
|
This is particularly useful if you are viewing documents with
|
|
|
|
|
color coded data in plots. This will maintain the colors such
|
|
|
|
|
that blue and red will remain these colors in the inverted
|
|
|
|
|
rendering. This inversion is non-trivial. This makes use of the
|
|
|
|
|
OKLab color space which is well calibrated to have equal
|
|
|
|
|
perceptual brightness across hue, but not all colors are within
|
|
|
|
|
the RGB gamut after inversion, causing some colors to saturate.
|
|
|
|
|
Nevertheless, this seems to work well in most cases."
|
|
|
|
|
:group 'pdf-view
|
|
|
|
|
:type 'boolean)
|
|
|
|
|
|
|
|
|
|
(defcustom pdf-view-change-page-hook nil
|
|
|
|
|
"Hook run after changing to another page, but before displaying it.
|
|
|
|
|
|
|
|
|
|
See also `pdf-view-before-change-page-hook' and
|
|
|
|
|
`pdf-view-after-change-page-hook'."
|
|
|
|
|
:group 'pdf-view
|
|
|
|
|
:type 'hook)
|
|
|
|
|
|
|
|
|
|
(defcustom pdf-view-before-change-page-hook nil
|
|
|
|
|
"Hook run before changing to another page.
|
|
|
|
|
|
|
|
|
|
See also `pdf-view-change-page-hook' and
|
|
|
|
|
`pdf-view-after-change-page-hook'."
|
|
|
|
|
:group 'pdf-view
|
|
|
|
|
:type 'hook)
|
|
|
|
|
|
|
|
|
|
(defcustom pdf-view-after-change-page-hook nil
|
|
|
|
|
"Hook run after changing to and displaying another page.
|
|
|
|
|
|
|
|
|
|
See also `pdf-view-change-page-hook' and
|
|
|
|
|
`pdf-view-before-change-page-hook'."
|
|
|
|
|
:group 'pdf-view
|
|
|
|
|
:type 'hook)
|
|
|
|
|
|
|
|
|
|
(defcustom pdf-view-use-dedicated-register t
|
|
|
|
|
"Whether to use dedicated register for PDF positions.
|
|
|
|
|
|
|
|
|
|
If this is non-nil, the commands `pdf-view-position-to-register'
|
|
|
|
|
and `pdf-view-jump-to-register' use the buffer-local variable
|
|
|
|
|
`pdf-view-register-alist' to store resp. retrieve marked
|
|
|
|
|
positions. Otherwise the common variable `register-alist' is
|
|
|
|
|
used."
|
|
|
|
|
:group 'pdf-view
|
|
|
|
|
:type 'boolean)
|
|
|
|
|
|
|
|
|
|
(defcustom pdf-view-image-relief 0
|
|
|
|
|
"Add a shadow rectangle around the page's image.
|
|
|
|
|
|
|
|
|
|
See :relief property in Info node `(elisp) Image Descriptors'."
|
|
|
|
|
:group 'pdf-view
|
|
|
|
|
:type '(integer :tag "Pixel")
|
|
|
|
|
:link '(info-link "(elisp) Image Descriptors"))
|
|
|
|
|
|
|
|
|
|
(defcustom pdf-view-max-image-width 4800
|
|
|
|
|
"Maximum width of any image displayed in pixel."
|
|
|
|
|
:group 'pdf-view
|
|
|
|
|
:type '(integer :tag "Pixel"))
|
|
|
|
|
|
|
|
|
|
(defcustom pdf-view-use-unicode-ligther t
|
|
|
|
|
"Decide whether to use unicode symbols in the mode-line.
|
|
|
|
|
|
|
|
|
|
On some systems finding a font which supports those symbols can
|
|
|
|
|
take some time. If you don't want to spend that time waiting and
|
|
|
|
|
don't care for a nicer looking mode-line, set this variable to
|
|
|
|
|
nil.
|
|
|
|
|
|
|
|
|
|
Note, that this option has only an effect when this library is
|
|
|
|
|
loaded."
|
|
|
|
|
:group 'pdf-view
|
|
|
|
|
:type 'boolean)
|
|
|
|
|
|
|
|
|
|
(defcustom pdf-view-incompatible-modes
|
|
|
|
|
'(linum-mode linum-relative-mode helm-linum-relative-mode
|
2023-08-10 14:03:04 +00:00
|
|
|
|
nlinum-mode nlinum-hl-mode nlinum-relative-mode yalinum-mode
|
|
|
|
|
display-line-numbers-mode)
|
2023-02-21 18:17:08 +00:00
|
|
|
|
"A list of modes incompatible with `pdf-view-mode'.
|
|
|
|
|
|
|
|
|
|
Issue a warning, if one of them is active in a PDF buffer."
|
|
|
|
|
:group 'pdf-view
|
|
|
|
|
:type '(repeat symbol))
|
|
|
|
|
|
|
|
|
|
(defcustom pdf-view-selection-style 'word
|
|
|
|
|
"The current default selection style.
|
|
|
|
|
|
|
|
|
|
Must be one of `glyph', `word', or `line'."
|
|
|
|
|
:group 'pdf-view
|
|
|
|
|
:type '(choice (const glyph)
|
|
|
|
|
(const word)
|
|
|
|
|
(const line)))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;; * ================================================================== *
|
|
|
|
|
;; * Internal variables and macros
|
|
|
|
|
;; * ================================================================== *
|
|
|
|
|
|
|
|
|
|
(defvar-local pdf-view-active-region nil
|
|
|
|
|
"The active region as a list of edges.
|
|
|
|
|
|
|
|
|
|
Edge values are relative coordinates.")
|
|
|
|
|
|
|
|
|
|
(defvar-local pdf-view--have-rectangle-region nil
|
|
|
|
|
"Non-nil if the region is currently rendered as a rectangle.
|
|
|
|
|
|
|
|
|
|
This variable is set in `pdf-view-mouse-set-region' and used in
|
|
|
|
|
`pdf-view-mouse-extend-region' to determine the right choice
|
|
|
|
|
regarding display of the region in the later function.")
|
|
|
|
|
|
|
|
|
|
(defvar-local pdf-view--buffer-file-name nil
|
|
|
|
|
"Local copy of remote file or nil.")
|
|
|
|
|
|
|
|
|
|
(defvar-local pdf-view--server-file-name nil
|
|
|
|
|
"The servers notion of this buffer's filename.")
|
|
|
|
|
|
|
|
|
|
(defvar-local pdf-view--next-page-timer nil
|
|
|
|
|
"Timer used in `pdf-view-next-page-command'.")
|
|
|
|
|
|
|
|
|
|
(defvar-local pdf-view--hotspot-functions nil
|
|
|
|
|
"Alist of hotspot functions.")
|
|
|
|
|
|
|
|
|
|
(defvar-local pdf-view--current-rotation nil
|
|
|
|
|
"Current rotation of the page.")
|
|
|
|
|
|
|
|
|
|
(defvar-local pdf-view-register-alist nil
|
|
|
|
|
"Local, dedicated register for PDF positions.")
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-current-pagelabel (&optional window)
|
|
|
|
|
(nth (1- (pdf-view-current-page window)) (pdf-info-pagelabels)))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-active-region-p nil
|
|
|
|
|
"Return t if there are active regions."
|
|
|
|
|
(not (null pdf-view-active-region)))
|
|
|
|
|
|
|
|
|
|
(defmacro pdf-view-assert-active-region ()
|
|
|
|
|
"Signal an error if there are no active regions."
|
|
|
|
|
`(unless (pdf-view-active-region-p)
|
|
|
|
|
(error "The region is not active")))
|
|
|
|
|
|
|
|
|
|
(defconst pdf-view-have-image-mode-pixel-vscroll
|
|
|
|
|
(>= emacs-major-version 27)
|
|
|
|
|
"Whether `image-mode' scrolls vertically by pixels.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;; * ================================================================== *
|
|
|
|
|
;; * Major Mode
|
|
|
|
|
;; * ================================================================== *
|
|
|
|
|
|
|
|
|
|
(defvar pdf-view-mode-map
|
|
|
|
|
(let ((map (make-sparse-keymap)))
|
|
|
|
|
(set-keymap-parent map image-mode-map)
|
|
|
|
|
(define-key map (kbd "Q") 'kill-this-buffer)
|
|
|
|
|
;; Navigation in the document
|
|
|
|
|
(define-key map (kbd "n") 'pdf-view-next-page-command)
|
|
|
|
|
(define-key map (kbd "p") 'pdf-view-previous-page-command)
|
|
|
|
|
(define-key map (kbd "<next>") 'forward-page)
|
|
|
|
|
(define-key map (kbd "<prior>") 'backward-page)
|
|
|
|
|
(define-key map [remap forward-page] 'pdf-view-next-page-command)
|
|
|
|
|
(define-key map [remap backward-page] 'pdf-view-previous-page-command)
|
|
|
|
|
(define-key map (kbd "SPC") 'pdf-view-scroll-up-or-next-page)
|
|
|
|
|
(define-key map (kbd "S-SPC") 'pdf-view-scroll-down-or-previous-page)
|
|
|
|
|
(define-key map (kbd "DEL") 'pdf-view-scroll-down-or-previous-page)
|
|
|
|
|
(define-key map (kbd "C-n") 'pdf-view-next-line-or-next-page)
|
|
|
|
|
(define-key map (kbd "<down>") 'pdf-view-next-line-or-next-page)
|
|
|
|
|
(define-key map [remap next-line] 'pdf-view-next-line-or-next-page)
|
|
|
|
|
(define-key map (kbd "C-p") 'pdf-view-previous-line-or-previous-page)
|
|
|
|
|
(define-key map (kbd "<up>") 'pdf-view-previous-line-or-previous-page)
|
|
|
|
|
(define-key map [remap previous-line] 'pdf-view-previous-line-or-previous-page)
|
|
|
|
|
(define-key map (kbd "M-<") 'pdf-view-first-page)
|
|
|
|
|
(define-key map [remap beginning-of-buffer] 'pdf-view-first-page)
|
|
|
|
|
(define-key map (kbd "M->") 'pdf-view-last-page)
|
|
|
|
|
(define-key map [remap end-of-buffer] 'pdf-view-last-page)
|
|
|
|
|
(define-key map [remap goto-line] 'pdf-view-goto-page)
|
|
|
|
|
(define-key map (kbd "M-g l") 'pdf-view-goto-label)
|
|
|
|
|
(define-key map (kbd "RET") 'image-next-line)
|
|
|
|
|
;; Zoom in/out.
|
|
|
|
|
(define-key map "+" 'pdf-view-enlarge)
|
|
|
|
|
(define-key map "=" 'pdf-view-enlarge)
|
|
|
|
|
(define-key map "-" 'pdf-view-shrink)
|
|
|
|
|
(define-key map "0" 'pdf-view-scale-reset)
|
|
|
|
|
;; Fit the image to the window
|
|
|
|
|
(define-key map "W" 'pdf-view-fit-width-to-window)
|
|
|
|
|
(define-key map "H" 'pdf-view-fit-height-to-window)
|
|
|
|
|
(define-key map "P" 'pdf-view-fit-page-to-window)
|
|
|
|
|
;; Slicing the image
|
|
|
|
|
(define-key map (kbd "s m") 'pdf-view-set-slice-using-mouse)
|
|
|
|
|
(define-key map (kbd "s b") 'pdf-view-set-slice-from-bounding-box)
|
|
|
|
|
(define-key map (kbd "s r") 'pdf-view-reset-slice)
|
|
|
|
|
;; Rotation.
|
|
|
|
|
(define-key map (kbd "R") #'pdf-view-rotate)
|
|
|
|
|
;; Reconvert
|
|
|
|
|
(define-key map (kbd "C-c C-c") 'doc-view-mode)
|
|
|
|
|
(define-key map (kbd "g") 'revert-buffer)
|
|
|
|
|
;; Region
|
|
|
|
|
(define-key map [down-mouse-1] 'pdf-view-mouse-set-region)
|
|
|
|
|
(define-key map [M-down-mouse-1] 'pdf-view-mouse-set-region-rectangle)
|
|
|
|
|
(define-key map [C-down-mouse-1] 'pdf-view-mouse-extend-region)
|
|
|
|
|
(define-key map [remap kill-region] 'pdf-view-kill-ring-save)
|
|
|
|
|
(define-key map [remap kill-ring-save] 'pdf-view-kill-ring-save)
|
|
|
|
|
(define-key map [remap mark-whole-buffer] 'pdf-view-mark-whole-page)
|
|
|
|
|
;; Other
|
|
|
|
|
(define-key map (kbd "C-c C-d") 'pdf-view-dark-minor-mode)
|
|
|
|
|
(define-key map (kbd "m") 'pdf-view-position-to-register)
|
|
|
|
|
(define-key map (kbd "'") 'pdf-view-jump-to-register)
|
|
|
|
|
|
|
|
|
|
(define-key map (kbd "C-c C-i") 'pdf-view-extract-region-image)
|
|
|
|
|
;; Rendering
|
|
|
|
|
(define-key map (kbd "C-c C-r m") 'pdf-view-midnight-minor-mode)
|
|
|
|
|
(define-key map (kbd "C-c C-r t") 'pdf-view-themed-minor-mode)
|
|
|
|
|
(define-key map (kbd "C-c C-r p") 'pdf-view-printer-minor-mode)
|
|
|
|
|
map)
|
|
|
|
|
"Keymap used by `pdf-view-mode' when displaying a doc as a set of images.")
|
|
|
|
|
|
2023-08-10 14:03:04 +00:00
|
|
|
|
(defvar pdf-tools-enabled-modes)
|
2023-02-21 18:17:08 +00:00
|
|
|
|
(define-derived-mode pdf-view-mode special-mode "PDFView"
|
|
|
|
|
"Major mode in PDF buffers.
|
|
|
|
|
|
|
|
|
|
PDFView Mode is an Emacs PDF viewer. It displays PDF files as
|
|
|
|
|
PNG images in Emacs buffers."
|
|
|
|
|
:group 'pdf-view
|
|
|
|
|
:abbrev-table nil
|
|
|
|
|
:syntax-table nil
|
|
|
|
|
;; Setup a local copy for remote files.
|
|
|
|
|
(when (and (or jka-compr-really-do-compress
|
|
|
|
|
(let ((file-name-handler-alist nil))
|
|
|
|
|
(not (and buffer-file-name
|
|
|
|
|
(file-readable-p buffer-file-name)))))
|
|
|
|
|
(pdf-tools-pdf-buffer-p))
|
|
|
|
|
(let ((tempfile (pdf-util-make-temp-file)))
|
|
|
|
|
(write-region nil nil tempfile nil 'no-message)
|
|
|
|
|
(setq-local pdf-view--buffer-file-name tempfile)))
|
|
|
|
|
;; Decryption needs to be done before any other function calls into
|
|
|
|
|
;; pdf-info.el (e.g. from the mode-line during redisplay during
|
|
|
|
|
;; waiting for process output).
|
|
|
|
|
(pdf-view-decrypt-document)
|
|
|
|
|
|
|
|
|
|
;; Setup scroll functions
|
|
|
|
|
(if (boundp 'mwheel-scroll-up-function) ; not --without-x build
|
|
|
|
|
(setq-local mwheel-scroll-up-function
|
|
|
|
|
#'pdf-view-scroll-up-or-next-page))
|
|
|
|
|
(if (boundp 'mwheel-scroll-down-function)
|
|
|
|
|
(setq-local mwheel-scroll-down-function
|
|
|
|
|
#'pdf-view-scroll-down-or-previous-page))
|
|
|
|
|
|
|
|
|
|
;; Disable pixel-scroll-precision-mode locally if enabled
|
|
|
|
|
(if (bound-and-true-p pixel-scroll-precision-mode)
|
|
|
|
|
(set (make-local-variable 'pixel-scroll-precision-mode) nil))
|
2023-08-10 14:03:04 +00:00
|
|
|
|
(if (boundp 'mwheel-coalesce-scroll-events)
|
|
|
|
|
(setq-local mwheel-coalesce-scroll-events t))
|
|
|
|
|
|
|
|
|
|
;; If the Emacs theme is dark, add `pdf-view-dark-minor-mode' to the
|
|
|
|
|
;; list of `pdf-tools-enabled-modes'. See an interesting discussion
|
|
|
|
|
;; at: https://github.com/vedang/pdf-tools/issues/166 about how this
|
|
|
|
|
;; avoids a segfault crash in MacOS Ventura. IF you know why this
|
|
|
|
|
;; happens, please get in touch via the linked issue.
|
|
|
|
|
|
|
|
|
|
(when (eq 'dark (frame-parameter nil 'background-mode))
|
|
|
|
|
(add-to-list 'pdf-tools-enabled-modes 'pdf-view-dark-minor-mode))
|
2023-02-21 18:17:08 +00:00
|
|
|
|
|
|
|
|
|
;; Clearing overlays
|
|
|
|
|
(add-hook 'change-major-mode-hook
|
|
|
|
|
(lambda ()
|
|
|
|
|
(remove-overlays (point-min) (point-max) 'pdf-view t))
|
|
|
|
|
nil t)
|
|
|
|
|
(remove-overlays (point-min) (point-max) 'pdf-view t) ;Just in case.
|
|
|
|
|
|
|
|
|
|
;; Setup other local variables.
|
|
|
|
|
(setq-local mode-line-position
|
|
|
|
|
'(" P" (:eval (number-to-string (pdf-view-current-page)))
|
|
|
|
|
;; Avoid errors during redisplay.
|
|
|
|
|
"/" (:eval (or (ignore-errors
|
|
|
|
|
(number-to-string (pdf-cache-number-of-pages)))
|
|
|
|
|
"???"))))
|
|
|
|
|
(setq-local auto-hscroll-mode nil)
|
|
|
|
|
(setq-local pdf-view--server-file-name (pdf-view-buffer-file-name))
|
|
|
|
|
;; High values of scroll-conservatively seem to trigger
|
|
|
|
|
;; some display bug in xdisp.c:try_scrolling .
|
|
|
|
|
(setq-local scroll-conservatively 0)
|
|
|
|
|
(setq-local cursor-type nil)
|
|
|
|
|
(setq-local buffer-read-only t)
|
|
|
|
|
(setq-local view-read-only nil)
|
|
|
|
|
(setq-local bookmark-make-record-function
|
|
|
|
|
'pdf-view-bookmark-make-record)
|
|
|
|
|
(setq-local revert-buffer-function #'pdf-view-revert-buffer)
|
|
|
|
|
;; No auto-save at the moment.
|
|
|
|
|
(setq-local buffer-auto-save-file-name nil)
|
|
|
|
|
;; Disable image rescaling.
|
|
|
|
|
(when (boundp 'image-scaling-factor)
|
|
|
|
|
(setq-local image-scaling-factor 1))
|
|
|
|
|
;; No undo at the moment.
|
|
|
|
|
(unless buffer-undo-list
|
|
|
|
|
(buffer-disable-undo))
|
|
|
|
|
;; Enable transient-mark-mode, so region deactivation when quitting
|
|
|
|
|
;; will work.
|
|
|
|
|
(setq-local transient-mark-mode t)
|
|
|
|
|
|
|
|
|
|
(add-hook 'window-configuration-change-hook
|
|
|
|
|
'pdf-view-redisplay-some-windows nil t)
|
|
|
|
|
(add-hook 'deactivate-mark-hook 'pdf-view-deactivate-region nil t)
|
|
|
|
|
(add-hook 'write-contents-functions
|
|
|
|
|
'pdf-view--write-contents-function nil t)
|
|
|
|
|
(add-hook 'kill-buffer-hook 'pdf-view-close-document nil t)
|
|
|
|
|
(pdf-view-add-hotspot-function
|
|
|
|
|
'pdf-view-text-regions-hotspots-function -9)
|
|
|
|
|
|
|
|
|
|
;; Keep track of display info
|
|
|
|
|
(add-hook 'image-mode-new-window-functions
|
|
|
|
|
'pdf-view-new-window-function nil t)
|
|
|
|
|
(image-mode-setup-winprops)
|
|
|
|
|
|
|
|
|
|
;; Issue a warning in the future about incompatible modes.
|
|
|
|
|
(run-with-timer 1 nil (lambda (buffer)
|
|
|
|
|
(when (buffer-live-p buffer)
|
|
|
|
|
(pdf-view-check-incompatible-modes buffer)))
|
|
|
|
|
(current-buffer)))
|
|
|
|
|
|
|
|
|
|
(advice-add 'cua-copy-region
|
|
|
|
|
:before-until
|
|
|
|
|
#'cua-copy-region--pdf-view-advice)
|
|
|
|
|
|
|
|
|
|
(defun cua-copy-region--pdf-view-advice (&rest _)
|
|
|
|
|
"If the current buffer is in `pdf-view' mode, call `pdf-view-kill-ring-save'."
|
|
|
|
|
(when (eq major-mode 'pdf-view-mode)
|
|
|
|
|
(pdf-view-kill-ring-save)
|
|
|
|
|
t))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-check-incompatible-modes (&optional buffer)
|
|
|
|
|
"Check BUFFER for incompatible modes, maybe issue a warning."
|
|
|
|
|
(with-current-buffer (or buffer (current-buffer))
|
|
|
|
|
(let ((modes
|
|
|
|
|
(cl-remove-if-not
|
|
|
|
|
(lambda (mode) (and (symbolp mode)
|
|
|
|
|
(boundp mode)
|
|
|
|
|
(symbol-value mode)))
|
|
|
|
|
pdf-view-incompatible-modes)))
|
|
|
|
|
(when modes
|
|
|
|
|
(display-warning
|
|
|
|
|
'pdf-view
|
|
|
|
|
(format "These modes are incompatible with `pdf-view-mode',
|
|
|
|
|
please deactivate them (or customize pdf-view-incompatible-modes): %s"
|
|
|
|
|
(mapconcat #'symbol-name modes ",")))))))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-decrypt-document ()
|
|
|
|
|
"Read a password, if the document is encrypted and open it."
|
|
|
|
|
(interactive)
|
|
|
|
|
(when (pdf-info-encrypted-p)
|
2024-07-28 16:03:37 +00:00
|
|
|
|
(let ((fn (buffer-file-name))
|
|
|
|
|
(prompt "Enter password for pdf document: ")
|
2023-02-21 18:17:08 +00:00
|
|
|
|
(i 3)
|
2024-07-28 16:03:37 +00:00
|
|
|
|
key password)
|
|
|
|
|
|
|
|
|
|
(when fn
|
|
|
|
|
(setq prompt (format "Enter password for `%s': "
|
|
|
|
|
(abbreviate-file-name fn)))
|
|
|
|
|
(setq key (concat "/pdf-tools" fn))
|
|
|
|
|
;; First, try with a cached password
|
|
|
|
|
(when (setq password (password-read-from-cache key))
|
|
|
|
|
(ignore-errors (pdf-info-open nil password))
|
|
|
|
|
(when (pdf-info-encrypted-p)
|
|
|
|
|
(password-cache-remove key))))
|
|
|
|
|
|
2023-02-21 18:17:08 +00:00
|
|
|
|
(while (and (> i 0)
|
|
|
|
|
(pdf-info-encrypted-p))
|
|
|
|
|
(setq i (1- i))
|
2024-07-28 16:03:37 +00:00
|
|
|
|
;; Cached password was not present or valid, try reading a new password
|
|
|
|
|
;; without cache.
|
|
|
|
|
(setq password (password-read prompt))
|
2023-02-21 18:17:08 +00:00
|
|
|
|
(setq prompt "Invalid password, try again: ")
|
|
|
|
|
(ignore-errors (pdf-info-open nil password)))
|
|
|
|
|
(pdf-info-open nil password)
|
2024-07-28 16:03:37 +00:00
|
|
|
|
(when key
|
|
|
|
|
(password-cache-add key password))))
|
2023-02-21 18:17:08 +00:00
|
|
|
|
nil)
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-buffer-file-name ()
|
|
|
|
|
"Return the local filename of the PDF in the current buffer.
|
|
|
|
|
|
|
|
|
|
This may be different from variable `buffer-file-name' when
|
|
|
|
|
operating on a local copy of a remote file."
|
|
|
|
|
(or pdf-view--buffer-file-name
|
|
|
|
|
(buffer-file-name)))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view--write-contents-function ()
|
|
|
|
|
"Function for `write-contents-functions' to save the buffer."
|
|
|
|
|
(when (pdf-util-pdf-buffer-p)
|
|
|
|
|
(let ((tempfile (pdf-info-save pdf-view--server-file-name)))
|
|
|
|
|
(unwind-protect
|
|
|
|
|
(progn
|
|
|
|
|
;; Order matters here: We need to first copy the new
|
|
|
|
|
;; content (tempfile) to the PDF, and then close the PDF.
|
|
|
|
|
;; Since while closing the file (and freeing its resources
|
|
|
|
|
;; in the process), it may be immediately reopened due to
|
|
|
|
|
;; redisplay happening inside the pdf-info-close function
|
|
|
|
|
;; (while waiting for a response from the process.).
|
|
|
|
|
(copy-file tempfile (or (buffer-file-name)
|
|
|
|
|
(read-file-name
|
|
|
|
|
"File name to save PDF to: "))
|
|
|
|
|
t)
|
|
|
|
|
(pdf-info-close pdf-view--server-file-name)
|
|
|
|
|
|
|
|
|
|
(when pdf-view--buffer-file-name
|
|
|
|
|
(copy-file tempfile pdf-view--buffer-file-name t))
|
|
|
|
|
(clear-visited-file-modtime)
|
|
|
|
|
(set-buffer-modified-p nil)
|
|
|
|
|
(setq pdf-view--server-file-name
|
|
|
|
|
(pdf-view-buffer-file-name))
|
|
|
|
|
t)
|
|
|
|
|
(when (file-exists-p tempfile)
|
|
|
|
|
(delete-file tempfile))))))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view--after-revert ()
|
|
|
|
|
"Reload the local copy in case of a remote file, and close the document."
|
|
|
|
|
(when pdf-view--buffer-file-name
|
|
|
|
|
(write-region nil nil pdf-view--buffer-file-name nil 'no-message))
|
|
|
|
|
(pdf-info-close))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-revert-buffer (&optional ignore-auto noconfirm)
|
|
|
|
|
"Revert buffer while preserving current modes.
|
|
|
|
|
|
|
|
|
|
Optional parameters IGNORE-AUTO and NOCONFIRM are defined as in
|
|
|
|
|
`revert-buffer'."
|
|
|
|
|
(interactive (list (not current-prefix-arg)))
|
|
|
|
|
;; Bind to default so that we can use pdf-view-revert-buffer as
|
|
|
|
|
;; revert-buffer-function. A binding of nil is needed in Emacs 24.3, but in
|
|
|
|
|
;; later versions the semantics that nil means the default function should
|
|
|
|
|
;; not relied upon.
|
|
|
|
|
(let ((revert-buffer-function (when (fboundp #'revert-buffer--default)
|
|
|
|
|
#'revert-buffer--default))
|
|
|
|
|
(after-revert-hook
|
|
|
|
|
(cons #'pdf-view--after-revert
|
|
|
|
|
after-revert-hook)))
|
|
|
|
|
(prog1
|
|
|
|
|
(revert-buffer ignore-auto noconfirm 'preserve-modes)
|
2024-07-28 16:03:37 +00:00
|
|
|
|
(pdf-view-decrypt-document)
|
2023-02-21 18:17:08 +00:00
|
|
|
|
(pdf-view-redisplay t))))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-close-document ()
|
|
|
|
|
"Return immediately after closing document.
|
|
|
|
|
|
|
|
|
|
This function always succeeds. See also `pdf-info-close', which
|
|
|
|
|
does not return immediately."
|
|
|
|
|
(when (pdf-info-running-p)
|
|
|
|
|
(let ((pdf-info-asynchronous 'ignore))
|
|
|
|
|
(ignore-errors
|
|
|
|
|
(pdf-info-close)))))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;; * ================================================================== *
|
|
|
|
|
;; * Scaling
|
|
|
|
|
;; * ================================================================== *
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-fit-page-to-window ()
|
|
|
|
|
"Fit PDF to window.
|
|
|
|
|
|
|
|
|
|
Choose the larger of PDF's height and width, and fits that
|
|
|
|
|
dimension to window."
|
|
|
|
|
(interactive)
|
|
|
|
|
(setq pdf-view-display-size 'fit-page)
|
|
|
|
|
(image-set-window-vscroll 0)
|
|
|
|
|
(image-set-window-hscroll 0)
|
|
|
|
|
(pdf-view-redisplay t))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-fit-height-to-window ()
|
|
|
|
|
"Fit PDF height to window."
|
|
|
|
|
(interactive)
|
|
|
|
|
(setq pdf-view-display-size 'fit-height)
|
|
|
|
|
(image-set-window-vscroll 0)
|
|
|
|
|
(pdf-view-redisplay t))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-fit-width-to-window ()
|
|
|
|
|
"Fit PDF size to window."
|
|
|
|
|
(interactive)
|
|
|
|
|
(setq pdf-view-display-size 'fit-width)
|
|
|
|
|
(image-set-window-hscroll 0)
|
|
|
|
|
(pdf-view-redisplay t))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-enlarge (factor)
|
|
|
|
|
"Enlarge PDF by FACTOR.
|
|
|
|
|
|
|
|
|
|
When called interactively, uses the value of
|
|
|
|
|
`pdf-view-resize-factor'.
|
|
|
|
|
|
|
|
|
|
For example, (pdf-view-enlarge 1.25) increases size by 25%."
|
|
|
|
|
(interactive
|
|
|
|
|
(list (float pdf-view-resize-factor)))
|
|
|
|
|
(let* ((size (pdf-view-image-size))
|
|
|
|
|
(pagesize (pdf-cache-pagesize
|
|
|
|
|
(pdf-view-current-page)))
|
|
|
|
|
(scale (/ (float (car size))
|
|
|
|
|
(float (car pagesize)))))
|
|
|
|
|
(setq pdf-view-display-size
|
|
|
|
|
(* factor scale))
|
|
|
|
|
(pdf-view-redisplay t)))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-shrink (factor)
|
|
|
|
|
"Shrink PDF by FACTOR.
|
|
|
|
|
|
|
|
|
|
When called interactively, uses the value of
|
|
|
|
|
`pdf-view-resize-factor'.
|
|
|
|
|
|
|
|
|
|
For example, (pdf-view-shrink 1.25) decreases size by 20%."
|
|
|
|
|
(interactive
|
|
|
|
|
(list (float pdf-view-resize-factor)))
|
|
|
|
|
(pdf-view-enlarge (/ 1.0 factor)))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-scale-reset ()
|
|
|
|
|
"Reset PDF to its default set."
|
|
|
|
|
(interactive)
|
|
|
|
|
(setq pdf-view-display-size 1.0)
|
|
|
|
|
(pdf-view-redisplay t))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;; * ================================================================== *
|
|
|
|
|
;; * Rotation
|
|
|
|
|
;; * ================================================================== *
|
|
|
|
|
(defun pdf-view-rotate (angle)
|
|
|
|
|
"Rotate the current page by ANGLE degrees clockwise.
|
|
|
|
|
When called interactively, angle defaults to 90. Moreover, if
|
|
|
|
|
called interactively with a prefix argument, then rotate
|
|
|
|
|
anti-clockwise."
|
|
|
|
|
(interactive (list (if current-prefix-arg -90 90)))
|
|
|
|
|
(setq-local pdf-view--current-rotation
|
|
|
|
|
(mod (+ (or pdf-view--current-rotation 0)
|
|
|
|
|
angle)
|
|
|
|
|
360))
|
|
|
|
|
(pdf-view-redisplay t))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;; * ================================================================== *
|
|
|
|
|
;; * Moving by pages and scrolling
|
|
|
|
|
;; * ================================================================== *
|
|
|
|
|
|
|
|
|
|
(defvar pdf-view-inhibit-redisplay nil)
|
|
|
|
|
(defvar pdf-view-inhibit-hotspots nil)
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-goto-page (page &optional window)
|
|
|
|
|
"Go to PAGE in PDF.
|
|
|
|
|
|
|
|
|
|
If optional parameter WINDOW, go to PAGE in all `pdf-view'
|
|
|
|
|
windows."
|
|
|
|
|
(interactive
|
|
|
|
|
(list (if current-prefix-arg
|
|
|
|
|
(prefix-numeric-value current-prefix-arg)
|
|
|
|
|
(read-number "Page: "))))
|
|
|
|
|
(unless (and (>= page 1)
|
|
|
|
|
(<= page (pdf-cache-number-of-pages)))
|
|
|
|
|
(error "No such page: %d" page))
|
|
|
|
|
(unless window
|
|
|
|
|
(setq window
|
|
|
|
|
(if (pdf-util-pdf-window-p)
|
|
|
|
|
(selected-window)
|
|
|
|
|
t)))
|
|
|
|
|
(save-selected-window
|
|
|
|
|
;; Select the window for the hooks below.
|
|
|
|
|
(when (window-live-p window)
|
|
|
|
|
(select-window window 'norecord))
|
|
|
|
|
(let ((changing-p
|
|
|
|
|
(not (eq page (pdf-view-current-page window)))))
|
|
|
|
|
(when changing-p
|
|
|
|
|
(run-hooks 'pdf-view-before-change-page-hook)
|
|
|
|
|
(setf (pdf-view-current-page window) page)
|
|
|
|
|
(run-hooks 'pdf-view-change-page-hook))
|
|
|
|
|
(when (window-live-p window)
|
|
|
|
|
(pdf-view-redisplay window))
|
|
|
|
|
(when changing-p
|
|
|
|
|
(pdf-view-deactivate-region)
|
|
|
|
|
(force-mode-line-update)
|
|
|
|
|
(run-hooks 'pdf-view-after-change-page-hook))))
|
|
|
|
|
nil)
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-next-page (&optional n)
|
|
|
|
|
"View the next page in the PDF.
|
|
|
|
|
|
|
|
|
|
Optional parameter N moves N pages forward."
|
|
|
|
|
(interactive "p")
|
|
|
|
|
(pdf-view-goto-page (+ (pdf-view-current-page)
|
|
|
|
|
(or n 1))))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-previous-page (&optional n)
|
|
|
|
|
"View the previous page in the PDF.
|
|
|
|
|
|
|
|
|
|
Optional parameter N moves N pages backward."
|
|
|
|
|
(interactive "p")
|
|
|
|
|
(pdf-view-next-page (- (or n 1))))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-next-page-command (&optional n)
|
|
|
|
|
"View the next page in the PDF.
|
|
|
|
|
|
|
|
|
|
Optional parameter N moves N pages forward.
|
|
|
|
|
|
|
|
|
|
This command is a wrapper for `pdf-view-next-page' that will
|
|
|
|
|
indicate to the user if they are on the last page and more."
|
|
|
|
|
(declare (interactive-only pdf-view-next-page))
|
|
|
|
|
(interactive "p")
|
|
|
|
|
(unless n (setq n 1))
|
|
|
|
|
(when (> (+ (pdf-view-current-page) n)
|
|
|
|
|
(pdf-cache-number-of-pages))
|
|
|
|
|
(user-error "Last page"))
|
|
|
|
|
(when (< (+ (pdf-view-current-page) n) 1)
|
|
|
|
|
(user-error "First page"))
|
|
|
|
|
(let ((pdf-view-inhibit-redisplay t))
|
|
|
|
|
(pdf-view-goto-page
|
|
|
|
|
(+ (pdf-view-current-page) n)))
|
|
|
|
|
(force-mode-line-update)
|
|
|
|
|
(sit-for 0)
|
|
|
|
|
(when pdf-view--next-page-timer
|
|
|
|
|
(cancel-timer pdf-view--next-page-timer)
|
|
|
|
|
(setq pdf-view--next-page-timer nil))
|
|
|
|
|
(if (or (not (input-pending-p))
|
|
|
|
|
(and (> n 0)
|
|
|
|
|
(= (pdf-view-current-page)
|
|
|
|
|
(pdf-cache-number-of-pages)))
|
|
|
|
|
(and (< n 0)
|
|
|
|
|
(= (pdf-view-current-page) 1)))
|
|
|
|
|
(pdf-view-redisplay)
|
|
|
|
|
(setq pdf-view--next-page-timer
|
|
|
|
|
(run-with-idle-timer 0.001 nil 'pdf-view-redisplay (selected-window)))))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-previous-page-command (&optional n)
|
|
|
|
|
"View the previous page in the PDF.
|
|
|
|
|
|
|
|
|
|
Optional parameter N moves N pages backward.
|
|
|
|
|
|
|
|
|
|
This command is a wrapper for `pdf-view-previous-page'."
|
|
|
|
|
(declare (interactive-only pdf-view-previous-page))
|
|
|
|
|
(interactive "p")
|
|
|
|
|
(with-no-warnings
|
|
|
|
|
(pdf-view-next-page-command (- (or n 1)))))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-first-page ()
|
|
|
|
|
"View the first page."
|
|
|
|
|
(interactive)
|
|
|
|
|
(pdf-view-goto-page 1))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-last-page ()
|
|
|
|
|
"View the last page."
|
|
|
|
|
(interactive)
|
|
|
|
|
(pdf-view-goto-page (pdf-cache-number-of-pages)))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-scroll-up-or-next-page (&optional arg)
|
|
|
|
|
"Scroll page up ARG lines if possible, else go to the next page.
|
|
|
|
|
|
|
|
|
|
When `pdf-view-continuous' is non-nil, scrolling upward at the
|
|
|
|
|
bottom edge of the page moves to the next page. Otherwise, go to
|
|
|
|
|
next page only on typing SPC (ARG is nil)."
|
|
|
|
|
(interactive "P")
|
|
|
|
|
(if (or pdf-view-continuous (null arg))
|
|
|
|
|
(let ((hscroll (window-hscroll))
|
|
|
|
|
(cur-page (pdf-view-current-page))
|
|
|
|
|
(win-scroll (window-vscroll nil pdf-view-have-image-mode-pixel-vscroll))
|
|
|
|
|
(img-scroll (image-scroll-up arg)))
|
|
|
|
|
(when (or
|
|
|
|
|
;; There is no next line for the image to scroll to
|
|
|
|
|
(and img-scroll (= win-scroll img-scroll))
|
|
|
|
|
;; Workaround rounding/off-by-one issues.
|
|
|
|
|
(memq pdf-view-display-size
|
|
|
|
|
'(fit-height fit-page)))
|
|
|
|
|
(pdf-view-next-page)
|
|
|
|
|
(when (/= cur-page (pdf-view-current-page))
|
|
|
|
|
(image-bob)
|
|
|
|
|
(image-bol 1))
|
|
|
|
|
(image-set-window-hscroll hscroll)))
|
|
|
|
|
(image-scroll-up arg)))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-scroll-down-or-previous-page (&optional arg)
|
|
|
|
|
"Scroll page down ARG lines if possible, else go to the previous page.
|
|
|
|
|
|
|
|
|
|
When `pdf-view-continuous' is non-nil, scrolling downward at the
|
|
|
|
|
top edge of the page moves to the previous page. Otherwise, go
|
|
|
|
|
to previous page only on typing DEL (ARG is nil)."
|
|
|
|
|
(interactive "P")
|
|
|
|
|
(if (or pdf-view-continuous (null arg))
|
|
|
|
|
(let ((hscroll (window-hscroll))
|
|
|
|
|
(cur-page (pdf-view-current-page))
|
|
|
|
|
(win-scroll (window-vscroll nil pdf-view-have-image-mode-pixel-vscroll))
|
|
|
|
|
(img-scroll (image-scroll-down arg)))
|
|
|
|
|
(when (or
|
|
|
|
|
;; There is no previous line for the image to scroll to
|
|
|
|
|
(and img-scroll (= win-scroll img-scroll))
|
|
|
|
|
;; Workaround rounding/off-by-one issues.
|
|
|
|
|
(memq pdf-view-display-size
|
|
|
|
|
'(fit-height fit-page)))
|
|
|
|
|
(pdf-view-previous-page)
|
|
|
|
|
(when (/= cur-page (pdf-view-current-page))
|
|
|
|
|
(image-eob)
|
|
|
|
|
(image-bol 1))
|
|
|
|
|
(image-set-window-hscroll hscroll)))
|
|
|
|
|
(image-scroll-down arg)))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-next-line-or-next-page (&optional arg)
|
|
|
|
|
"Scroll upward by ARG lines if possible, else go to the next page.
|
|
|
|
|
|
|
|
|
|
When `pdf-view-continuous' is non-nil, scrolling a line upward
|
|
|
|
|
at the bottom edge of the page moves to the next page."
|
|
|
|
|
(interactive "p")
|
|
|
|
|
(if pdf-view-continuous
|
|
|
|
|
(let ((hscroll (window-hscroll))
|
|
|
|
|
(cur-page (pdf-view-current-page)))
|
|
|
|
|
(when (= (window-vscroll nil pdf-view-have-image-mode-pixel-vscroll)
|
|
|
|
|
(image-next-line arg))
|
|
|
|
|
(pdf-view-next-page)
|
|
|
|
|
(when (/= cur-page (pdf-view-current-page))
|
|
|
|
|
(image-bob)
|
|
|
|
|
(image-bol 1))
|
|
|
|
|
(image-set-window-hscroll hscroll)))
|
2024-07-28 16:03:37 +00:00
|
|
|
|
(image-next-line arg)))
|
2023-02-21 18:17:08 +00:00
|
|
|
|
|
|
|
|
|
(defun pdf-view-previous-line-or-previous-page (&optional arg)
|
|
|
|
|
"Scroll downward by ARG lines if possible, else go to the previous page.
|
|
|
|
|
|
|
|
|
|
When `pdf-view-continuous' is non-nil, scrolling a line downward
|
|
|
|
|
at the top edge of the page moves to the previous page."
|
|
|
|
|
(interactive "p")
|
|
|
|
|
(if pdf-view-continuous
|
|
|
|
|
(let ((hscroll (window-hscroll))
|
|
|
|
|
(cur-page (pdf-view-current-page)))
|
|
|
|
|
(when (= (window-vscroll nil pdf-view-have-image-mode-pixel-vscroll)
|
|
|
|
|
(image-previous-line arg))
|
|
|
|
|
(pdf-view-previous-page)
|
|
|
|
|
(when (/= cur-page (pdf-view-current-page))
|
|
|
|
|
(image-eob)
|
|
|
|
|
(image-bol 1))
|
|
|
|
|
(image-set-window-hscroll hscroll)))
|
|
|
|
|
(image-previous-line arg)))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-goto-label (label)
|
|
|
|
|
"Go to the page corresponding to LABEL.
|
|
|
|
|
|
|
|
|
|
Usually, the label of a document's page is the same as its
|
|
|
|
|
displayed page number."
|
|
|
|
|
(interactive
|
|
|
|
|
(list (let ((labels (pdf-info-pagelabels)))
|
|
|
|
|
(completing-read "Goto label: " labels nil t))))
|
|
|
|
|
(let ((index (cl-position label (pdf-info-pagelabels) :test 'equal)))
|
|
|
|
|
(unless index
|
|
|
|
|
(error "No such label: %s" label))
|
|
|
|
|
(pdf-view-goto-page (1+ index))))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-center-in-window ()
|
|
|
|
|
"Center PDF in window horizontally."
|
|
|
|
|
(interactive)
|
|
|
|
|
(image-set-window-hscroll
|
|
|
|
|
(/ (* (- (car (pdf-view-image-size))
|
|
|
|
|
(window-pixel-width))
|
|
|
|
|
(window-width))
|
|
|
|
|
2 (window-pixel-width))) ; convert from pixel to character width
|
|
|
|
|
(pdf-view-redisplay t))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-align-left ()
|
|
|
|
|
"Align left edge of pdf with left edge of window."
|
|
|
|
|
(interactive)
|
|
|
|
|
(image-set-window-hscroll 0)
|
|
|
|
|
(pdf-view-redisplay t))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-align-right ()
|
|
|
|
|
"Align right edge of pdf with right edge of window."
|
|
|
|
|
(interactive)
|
|
|
|
|
(image-set-window-hscroll
|
|
|
|
|
(/ (* (- (car (pdf-view-image-size))
|
|
|
|
|
(window-pixel-width))
|
|
|
|
|
(window-width))
|
|
|
|
|
(window-pixel-width))) ; convert from pixel to character width
|
|
|
|
|
(pdf-view-redisplay t))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;; * ================================================================== *
|
|
|
|
|
;; * Slicing
|
|
|
|
|
;; * ================================================================== *
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-set-slice (x y width height &optional window)
|
|
|
|
|
"Set the slice of the pages that should be displayed in WINDOW.
|
|
|
|
|
|
|
|
|
|
WINDOW defaults to `selected-window' if not provided.
|
|
|
|
|
X, Y, WIDTH and HEIGHT should be relative coordinates, i.e. in
|
|
|
|
|
\[0;1\]. To reset the slice use `pdf-view-reset-slice'."
|
|
|
|
|
(unless (equal (pdf-view-current-slice window)
|
|
|
|
|
(list x y width height))
|
|
|
|
|
(setf (pdf-view-current-slice window)
|
|
|
|
|
(mapcar (lambda (v)
|
|
|
|
|
(max 0 (min 1 v)))
|
|
|
|
|
(list x y width height)))
|
|
|
|
|
(pdf-view-redisplay window)))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-set-slice-using-mouse ()
|
|
|
|
|
"Set the slice of the images that should be displayed.
|
|
|
|
|
|
|
|
|
|
Set the slice by pressing `mouse-1' at its top-left corner and
|
|
|
|
|
dragging it to its bottom-right corner. See also
|
|
|
|
|
`pdf-view-set-slice' and `pdf-view-reset-slice'."
|
|
|
|
|
(interactive)
|
|
|
|
|
(let ((size (pdf-view-image-size))
|
|
|
|
|
x y w h done)
|
|
|
|
|
(while (not done)
|
|
|
|
|
(let ((e (read-event
|
|
|
|
|
(concat "Press mouse-1 at the top-left corner and "
|
|
|
|
|
"drag it to the bottom-right corner!"))))
|
|
|
|
|
(when (eq (car e) 'drag-mouse-1)
|
|
|
|
|
(setq x (car (posn-object-x-y (event-start e))))
|
|
|
|
|
(setq y (cdr (posn-object-x-y (event-start e))))
|
|
|
|
|
(setq w (- (car (posn-object-x-y (event-end e))) x))
|
|
|
|
|
(setq h (- (cdr (posn-object-x-y (event-end e))) y))
|
|
|
|
|
(setq done t))))
|
|
|
|
|
(apply 'pdf-view-set-slice
|
|
|
|
|
(pdf-util-scale
|
|
|
|
|
(list x y w h)
|
|
|
|
|
(cons (/ 1.0 (float (car size)))
|
|
|
|
|
(/ 1.0 (float (cdr size))))))))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-set-slice-from-bounding-box (&optional window)
|
|
|
|
|
"Set the slice from the page's bounding-box.
|
|
|
|
|
|
|
|
|
|
WINDOW defaults to `selected-window' if not provided.
|
|
|
|
|
|
|
|
|
|
The result is that the margins are almost completely cropped,
|
|
|
|
|
much more accurate than could be done manually using
|
|
|
|
|
`pdf-view-set-slice-using-mouse'.
|
|
|
|
|
|
|
|
|
|
See also `pdf-view-bounding-box-margin'."
|
|
|
|
|
(interactive)
|
|
|
|
|
(let* ((bb (pdf-cache-boundingbox (pdf-view-current-page window)))
|
|
|
|
|
(margin (max 0 (or pdf-view-bounding-box-margin 0)))
|
|
|
|
|
(slice (list (- (nth 0 bb)
|
|
|
|
|
(/ margin 2.0))
|
|
|
|
|
(- (nth 1 bb)
|
|
|
|
|
(/ margin 2.0))
|
|
|
|
|
(+ (- (nth 2 bb) (nth 0 bb))
|
|
|
|
|
margin)
|
|
|
|
|
(+ (- (nth 3 bb) (nth 1 bb))
|
|
|
|
|
margin))))
|
|
|
|
|
(apply 'pdf-view-set-slice
|
|
|
|
|
(append slice (and window (list window))))))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-reset-slice (&optional window)
|
|
|
|
|
"Reset the current slice and redisplay WINDOW.
|
|
|
|
|
|
|
|
|
|
WINDOW defaults to `selected-window' if not provided.
|
|
|
|
|
|
|
|
|
|
After calling this function the whole page will be visible
|
|
|
|
|
again."
|
|
|
|
|
(interactive)
|
|
|
|
|
(when (pdf-view-current-slice window)
|
|
|
|
|
(setf (pdf-view-current-slice window) nil)
|
|
|
|
|
(pdf-view-redisplay window))
|
|
|
|
|
nil)
|
|
|
|
|
|
|
|
|
|
(define-minor-mode pdf-view-auto-slice-minor-mode
|
|
|
|
|
"Automatically slice pages according to their bounding boxes.
|
|
|
|
|
|
|
|
|
|
See also `pdf-view-set-slice-from-bounding-box'."
|
|
|
|
|
:group 'pdf-view
|
|
|
|
|
(pdf-util-assert-pdf-buffer)
|
|
|
|
|
(cond
|
|
|
|
|
(pdf-view-auto-slice-minor-mode
|
|
|
|
|
(dolist (win (get-buffer-window-list nil nil t))
|
|
|
|
|
(when (pdf-util-pdf-window-p win)
|
|
|
|
|
(pdf-view-set-slice-from-bounding-box win)))
|
|
|
|
|
(add-hook 'pdf-view-change-page-hook
|
|
|
|
|
'pdf-view-set-slice-from-bounding-box nil t))
|
|
|
|
|
(t
|
|
|
|
|
(progn (remove-hook 'pdf-view-change-page-hook
|
|
|
|
|
'pdf-view-set-slice-from-bounding-box t)
|
|
|
|
|
(pdf-view-reset-slice)))))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;; * ================================================================== *
|
|
|
|
|
;; * Display
|
|
|
|
|
;; * ================================================================== *
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-image-type ()
|
|
|
|
|
"Return the image type that should be used.
|
|
|
|
|
|
|
|
|
|
The return value is either `imagemagick' (if available and wanted
|
|
|
|
|
or if png is not available) or `png'.
|
|
|
|
|
|
|
|
|
|
Signal an error, if neither `imagemagick' nor `png' is available.
|
|
|
|
|
|
|
|
|
|
See also `pdf-view-use-imagemagick'."
|
|
|
|
|
(cond ((and pdf-view-use-imagemagick
|
|
|
|
|
(fboundp 'imagemagick-types))
|
|
|
|
|
'imagemagick)
|
|
|
|
|
((image-type-available-p 'image-io)
|
|
|
|
|
'image-io)
|
|
|
|
|
((image-type-available-p 'png)
|
|
|
|
|
'png)
|
|
|
|
|
((fboundp 'imagemagick-types)
|
|
|
|
|
'imagemagick)
|
|
|
|
|
(t
|
|
|
|
|
(error "PNG image supported not compiled into Emacs"))))
|
|
|
|
|
|
|
|
|
|
(defmacro pdf-view-create-image (data &rest props)
|
|
|
|
|
;; TODO: add DATA and PROPS to docstring.
|
|
|
|
|
"Like `create-image', but with set DATA-P and TYPE arguments."
|
|
|
|
|
(declare (indent 1) (debug t))
|
|
|
|
|
(let ((image-data (make-symbol "data")))
|
|
|
|
|
`(let ((,image-data ,data))
|
|
|
|
|
(apply #'create-image ,image-data (pdf-view-image-type) t ,@props
|
|
|
|
|
(cl-list*
|
|
|
|
|
:relief (or pdf-view-image-relief 0)
|
|
|
|
|
(when (and (eq (framep-on-display) 'mac)
|
|
|
|
|
(= (pdf-util-frame-scale-factor) 2))
|
|
|
|
|
(list :data-2x ,image-data)))))))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-create-page (page &optional window)
|
|
|
|
|
"Create an image of PAGE for display on WINDOW."
|
|
|
|
|
(let* ((size (pdf-view-desired-image-size page window))
|
|
|
|
|
(data (pdf-cache-renderpage
|
|
|
|
|
page (car size)
|
|
|
|
|
(if pdf-view-use-scaling
|
|
|
|
|
(* 2 (car size))
|
|
|
|
|
(car size))))
|
|
|
|
|
(hotspots (pdf-view-apply-hotspot-functions
|
|
|
|
|
window page size)))
|
|
|
|
|
(pdf-view-create-image data
|
|
|
|
|
:width (car size)
|
|
|
|
|
:rotation (or pdf-view--current-rotation 0)
|
|
|
|
|
:map hotspots
|
|
|
|
|
:pointer 'arrow)))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-image-size (&optional displayed-p window)
|
|
|
|
|
;; TODO: add WINDOW to docstring.
|
|
|
|
|
"Return the size in pixel of the current image.
|
|
|
|
|
|
|
|
|
|
If DISPLAYED-P is non-nil, return the size of the displayed
|
|
|
|
|
image. These values may be different, if slicing is used."
|
|
|
|
|
(if displayed-p
|
|
|
|
|
(with-selected-window (or window (selected-window))
|
|
|
|
|
(image-display-size
|
|
|
|
|
(image-get-display-property) t))
|
|
|
|
|
(image-size (pdf-view-current-image window) t)))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-image-offset (&optional window)
|
|
|
|
|
;; TODO: add WINDOW to docstring.
|
|
|
|
|
"Return the offset of the current image.
|
|
|
|
|
|
|
|
|
|
It is equal to \(LEFT . TOP\) of the current slice in pixel."
|
|
|
|
|
(let* ((slice (pdf-view-current-slice window)))
|
|
|
|
|
(cond
|
|
|
|
|
(slice
|
|
|
|
|
(pdf-util-scale-relative-to-pixel
|
|
|
|
|
(cons (nth 0 slice) (nth 1 slice))
|
|
|
|
|
window))
|
|
|
|
|
(t
|
|
|
|
|
(cons 0 0)))))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-display-page (page &optional window)
|
|
|
|
|
"Display page PAGE in WINDOW."
|
|
|
|
|
(setf (pdf-view-window-needs-redisplay window) nil)
|
|
|
|
|
(pdf-view-display-image
|
|
|
|
|
(pdf-view-create-page page window)
|
|
|
|
|
window))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-display-image (image &optional window inhibit-slice-p)
|
|
|
|
|
;; TODO: write documentation!
|
|
|
|
|
(let ((ol (pdf-view-current-overlay window)))
|
|
|
|
|
(when (window-live-p (overlay-get ol 'window))
|
|
|
|
|
(let* ((size (image-size image t))
|
|
|
|
|
(slice (if (not inhibit-slice-p)
|
|
|
|
|
(pdf-view-current-slice window)))
|
|
|
|
|
(displayed-width (floor
|
|
|
|
|
(if slice
|
|
|
|
|
(* (nth 2 slice)
|
|
|
|
|
(car (image-size image)))
|
|
|
|
|
(car (image-size image))))))
|
|
|
|
|
(setf (pdf-view-current-image window) image)
|
|
|
|
|
(move-overlay ol (point-min) (point-max))
|
|
|
|
|
;; In case the window is wider than the image, center the image
|
|
|
|
|
;; horizontally.
|
|
|
|
|
(overlay-put ol 'before-string
|
|
|
|
|
(when (> (window-width window)
|
|
|
|
|
displayed-width)
|
|
|
|
|
(propertize " " 'display
|
|
|
|
|
`(space :align-to
|
|
|
|
|
,(/ (- (window-width window)
|
|
|
|
|
displayed-width) 2)))))
|
|
|
|
|
(overlay-put ol 'display
|
|
|
|
|
(if slice
|
|
|
|
|
(list (cons 'slice
|
|
|
|
|
(pdf-util-scale slice size 'round))
|
|
|
|
|
image)
|
|
|
|
|
image))
|
|
|
|
|
(let* ((win (overlay-get ol 'window))
|
|
|
|
|
(hscroll (image-mode-window-get 'hscroll win))
|
|
|
|
|
(vscroll (image-mode-window-get 'vscroll win)))
|
|
|
|
|
;; Reset scroll settings, in case they were changed.
|
|
|
|
|
(if hscroll (set-window-hscroll win hscroll))
|
|
|
|
|
(if vscroll (set-window-vscroll
|
|
|
|
|
win vscroll pdf-view-have-image-mode-pixel-vscroll)))))))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-redisplay (&optional window)
|
|
|
|
|
"Redisplay page in WINDOW.
|
|
|
|
|
|
|
|
|
|
If WINDOW is t, redisplay pages in all windows."
|
|
|
|
|
(unless pdf-view-inhibit-redisplay
|
|
|
|
|
(if (not (eq t window))
|
|
|
|
|
(pdf-view-display-page
|
|
|
|
|
(pdf-view-current-page window)
|
|
|
|
|
window)
|
|
|
|
|
(dolist (win (get-buffer-window-list nil nil t))
|
|
|
|
|
(pdf-view-display-page
|
|
|
|
|
(pdf-view-current-page win)
|
|
|
|
|
win))
|
|
|
|
|
(when (consp image-mode-winprops-alist)
|
|
|
|
|
(dolist (window (mapcar #'car image-mode-winprops-alist))
|
|
|
|
|
(unless (or (not (window-live-p window))
|
|
|
|
|
(eq (current-buffer)
|
|
|
|
|
(window-buffer window)))
|
|
|
|
|
(setf (pdf-view-window-needs-redisplay window) t)))))
|
|
|
|
|
(force-mode-line-update)))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-redisplay-pages (&rest pages)
|
|
|
|
|
"Redisplay PAGES in all windows."
|
|
|
|
|
(pdf-util-assert-pdf-buffer)
|
|
|
|
|
(dolist (window (get-buffer-window-list nil nil t))
|
|
|
|
|
(when (memq (pdf-view-current-page window)
|
|
|
|
|
pages)
|
|
|
|
|
(pdf-view-redisplay window))))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-maybe-redisplay-resized-windows ()
|
|
|
|
|
"Redisplay some windows needing redisplay."
|
|
|
|
|
(unless (or (numberp pdf-view-display-size)
|
|
|
|
|
(pdf-view-active-region-p)
|
|
|
|
|
(> (minibuffer-depth) 0))
|
|
|
|
|
(dolist (window (get-buffer-window-list nil nil t))
|
|
|
|
|
(let ((stored (pdf-view-current-window-size window))
|
|
|
|
|
(size (cons (window-width window)
|
|
|
|
|
(window-height window))))
|
|
|
|
|
(unless (equal size stored)
|
|
|
|
|
(setf (pdf-view-current-window-size window) size)
|
|
|
|
|
(unless (or (null stored)
|
|
|
|
|
(and (eq pdf-view-display-size 'fit-width)
|
|
|
|
|
(eq (car size) (car stored)))
|
|
|
|
|
(and (eq pdf-view-display-size 'fit-height)
|
|
|
|
|
(eq (cdr size) (cdr stored))))
|
|
|
|
|
(pdf-view-redisplay window)))))))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-redisplay-some-windows ()
|
|
|
|
|
(pdf-view-maybe-redisplay-resized-windows)
|
|
|
|
|
(dolist (window (get-buffer-window-list nil nil t))
|
|
|
|
|
(when (pdf-view-window-needs-redisplay window)
|
|
|
|
|
(pdf-view-redisplay window))))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-new-window-function (winprops)
|
|
|
|
|
;; TODO: write documentation!
|
|
|
|
|
;; (message "New window %s for buf %s" (car winprops) (current-buffer))
|
|
|
|
|
(cl-assert (or (eq t (car winprops))
|
|
|
|
|
(eq (window-buffer (car winprops)) (current-buffer))))
|
|
|
|
|
(let ((ol (image-mode-window-get 'overlay winprops)))
|
|
|
|
|
(if ol
|
|
|
|
|
(progn
|
|
|
|
|
(setq ol (copy-overlay ol))
|
|
|
|
|
;; `ol' might actually be dead.
|
|
|
|
|
(move-overlay ol (point-min) (point-max)))
|
|
|
|
|
(setq ol (make-overlay (point-min) (point-max) nil t))
|
|
|
|
|
(overlay-put ol 'pdf-view t))
|
|
|
|
|
(overlay-put ol 'window (car winprops))
|
|
|
|
|
(unless (windowp (car winprops))
|
|
|
|
|
;; It's a pseudo entry. Let's make sure it's not displayed (the
|
|
|
|
|
;; `window' property is only effective if its value is a window).
|
|
|
|
|
(cl-assert (eq t (car winprops)))
|
|
|
|
|
(delete-overlay ol))
|
|
|
|
|
(image-mode-window-put 'overlay ol winprops)
|
|
|
|
|
;; Clean up some overlays.
|
|
|
|
|
(dolist (ov (overlays-in (point-min) (point-max)))
|
|
|
|
|
(when (and (windowp (overlay-get ov 'window))
|
|
|
|
|
(not (window-live-p (overlay-get ov 'window))))
|
|
|
|
|
(delete-overlay ov)))
|
|
|
|
|
(when (and (windowp (car winprops))
|
|
|
|
|
(null (image-mode-window-get 'image winprops)))
|
|
|
|
|
;; We're not displaying an image yet, so let's do so. This
|
|
|
|
|
;; happens when the buffer is displayed for the first time.
|
|
|
|
|
(with-selected-window (car winprops)
|
|
|
|
|
(pdf-view-goto-page
|
|
|
|
|
(or (image-mode-window-get 'page t) 1))))))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-desired-image-size (&optional page window)
|
|
|
|
|
;; TODO: write documentation!
|
|
|
|
|
(let* ((pagesize (pdf-cache-pagesize
|
|
|
|
|
(or page (pdf-view-current-page window))))
|
|
|
|
|
(slice (pdf-view-current-slice window))
|
|
|
|
|
(width-scale (/ (/ (float (window-body-width window t))
|
|
|
|
|
(or (nth 2 slice) 1.0))
|
|
|
|
|
(float (car pagesize))))
|
|
|
|
|
(height (- (nth 3 (window-inside-pixel-edges window))
|
|
|
|
|
(nth 1 (window-inside-pixel-edges window))
|
|
|
|
|
1))
|
|
|
|
|
(height-scale (/ (/ (float height)
|
|
|
|
|
(or (nth 3 slice) 1.0))
|
|
|
|
|
(float (cdr pagesize))))
|
|
|
|
|
(scale width-scale))
|
|
|
|
|
(if (numberp pdf-view-display-size)
|
|
|
|
|
(setq scale (float pdf-view-display-size))
|
|
|
|
|
(cl-case pdf-view-display-size
|
|
|
|
|
(fit-page
|
|
|
|
|
(setq scale (min height-scale width-scale)))
|
|
|
|
|
(fit-height
|
|
|
|
|
(setq scale height-scale))
|
|
|
|
|
(t
|
|
|
|
|
(setq scale width-scale))))
|
|
|
|
|
(let ((width (floor (* (car pagesize) scale)))
|
|
|
|
|
(height (floor (* (cdr pagesize) scale))))
|
|
|
|
|
(when (> width (max 1 (or pdf-view-max-image-width width)))
|
|
|
|
|
(setq width pdf-view-max-image-width
|
|
|
|
|
height (* height (/ (float pdf-view-max-image-width) width))))
|
|
|
|
|
(cons (max 1 width) (max 1 height)))))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-text-regions-hotspots-function (page size)
|
|
|
|
|
"Return a list of hotspots for text regions on PAGE using SIZE.
|
|
|
|
|
|
|
|
|
|
This will display a text cursor, when hovering over them."
|
|
|
|
|
(local-set-key [pdf-view-text-region t]
|
|
|
|
|
'pdf-util-image-map-mouse-event-proxy)
|
|
|
|
|
(mapcar (lambda (region)
|
|
|
|
|
(let ((e (pdf-util-scale region size 'round)))
|
|
|
|
|
`((rect . ((,(nth 0 e) . ,(nth 1 e))
|
|
|
|
|
. (,(nth 2 e) . ,(nth 3 e))))
|
|
|
|
|
pdf-view-text-region
|
|
|
|
|
(pointer text))))
|
|
|
|
|
(pdf-cache-textregions page)))
|
|
|
|
|
|
|
|
|
|
(define-minor-mode pdf-view-dark-minor-mode
|
|
|
|
|
"Mode for PDF documents with dark background.
|
|
|
|
|
|
|
|
|
|
This tells the various modes to use their face's dark colors."
|
|
|
|
|
:group 'pdf-view
|
|
|
|
|
(pdf-util-assert-pdf-buffer)
|
|
|
|
|
;; FIXME: This should really be run in a hook.
|
|
|
|
|
(when (bound-and-true-p pdf-isearch-active-mode)
|
|
|
|
|
(with-no-warnings
|
|
|
|
|
(pdf-isearch-redisplay)
|
|
|
|
|
(pdf-isearch-message
|
|
|
|
|
(if pdf-view-dark-minor-mode "dark mode" "light mode")))))
|
|
|
|
|
|
|
|
|
|
(define-minor-mode pdf-view-printer-minor-mode
|
|
|
|
|
"Display the PDF as it would be printed."
|
|
|
|
|
:group 'pdf-view
|
|
|
|
|
:lighter " Prn"
|
|
|
|
|
(pdf-util-assert-pdf-buffer)
|
|
|
|
|
(let ((enable (lambda ()
|
|
|
|
|
(pdf-info-setoptions :render/printed t))))
|
|
|
|
|
(cond
|
|
|
|
|
(pdf-view-printer-minor-mode
|
|
|
|
|
(add-hook 'after-save-hook enable nil t)
|
|
|
|
|
(add-hook 'after-revert-hook enable nil t))
|
|
|
|
|
(t
|
|
|
|
|
(remove-hook 'after-save-hook enable t)
|
|
|
|
|
(remove-hook 'after-revert-hook enable t))))
|
|
|
|
|
(pdf-info-setoptions :render/printed pdf-view-printer-minor-mode)
|
|
|
|
|
(pdf-cache-clear-images)
|
|
|
|
|
(pdf-view-redisplay t))
|
|
|
|
|
|
|
|
|
|
(define-minor-mode pdf-view-midnight-minor-mode
|
|
|
|
|
"Apply a color-filter appropriate for past midnight reading.
|
|
|
|
|
|
|
|
|
|
The colors are determined by the variable
|
|
|
|
|
`pdf-view-midnight-colors', which see. "
|
|
|
|
|
:group 'pdf-view
|
|
|
|
|
:lighter " Mid"
|
|
|
|
|
(pdf-util-assert-pdf-buffer)
|
|
|
|
|
;; FIXME: Maybe these options should be passed stateless to pdf-info-renderpage ?
|
|
|
|
|
(let ((enable (lambda ()
|
|
|
|
|
(pdf-info-setoptions
|
|
|
|
|
:render/foreground (or (car pdf-view-midnight-colors) "black")
|
|
|
|
|
:render/background (or (cdr pdf-view-midnight-colors) "white")
|
|
|
|
|
:render/usecolors
|
|
|
|
|
(if pdf-view-midnight-invert
|
|
|
|
|
;; If midnight invert is enabled, pass "2" indicating
|
|
|
|
|
;; that :render/foreground and :render/background should
|
|
|
|
|
;; be ignored and to instead invert the PDF (preserving
|
|
|
|
|
;; hue)
|
|
|
|
|
2
|
|
|
|
|
;; If invert is not enabled, pass "1" indictating that
|
|
|
|
|
;; :render/foreground and :render/background should be used
|
|
|
|
|
1)))))
|
|
|
|
|
(cond
|
|
|
|
|
(pdf-view-midnight-minor-mode
|
|
|
|
|
(add-hook 'after-save-hook enable nil t)
|
|
|
|
|
(add-hook 'after-revert-hook enable nil t)
|
|
|
|
|
(funcall enable))
|
|
|
|
|
(t
|
|
|
|
|
(remove-hook 'after-save-hook enable t)
|
|
|
|
|
(remove-hook 'after-revert-hook enable t)
|
|
|
|
|
(pdf-info-setoptions
|
|
|
|
|
;; Value "0" indicates that colors should remain unchanged
|
|
|
|
|
:render/usecolors 0))))
|
|
|
|
|
(pdf-cache-clear-images)
|
|
|
|
|
(pdf-view-redisplay t))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-set-theme-background ()
|
|
|
|
|
"Set the buffer's color filter to correspond to the current Emacs theme."
|
|
|
|
|
(pdf-util-assert-pdf-buffer)
|
|
|
|
|
(pdf-info-setoptions
|
|
|
|
|
:render/foreground (face-foreground 'default nil)
|
|
|
|
|
:render/background (face-background 'default nil)
|
|
|
|
|
:render/usecolors 1))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-refresh-themed-buffer (&optional get-theme)
|
|
|
|
|
"Refresh the current buffer to activate applied colors.
|
|
|
|
|
|
|
|
|
|
When GET-THEME is non-nil, also reset the applied colors to the
|
|
|
|
|
current theme's colors."
|
|
|
|
|
(pdf-util-assert-pdf-buffer)
|
|
|
|
|
(pdf-cache-clear-images)
|
|
|
|
|
(when get-theme
|
|
|
|
|
(pdf-view-set-theme-background))
|
|
|
|
|
(pdf-view-redisplay t))
|
|
|
|
|
|
|
|
|
|
(define-minor-mode pdf-view-themed-minor-mode
|
|
|
|
|
"Synchronize color filter with the present Emacs theme.
|
|
|
|
|
|
|
|
|
|
The colors are determined by the `face-foreground' and
|
|
|
|
|
`face-background' of the currently active theme."
|
|
|
|
|
:group 'pdf-view
|
|
|
|
|
:lighter " Thm"
|
|
|
|
|
(pdf-util-assert-pdf-buffer)
|
|
|
|
|
(cond
|
|
|
|
|
(pdf-view-themed-minor-mode
|
|
|
|
|
(add-hook 'after-save-hook #'pdf-view-set-theme-background nil t)
|
|
|
|
|
(add-hook 'after-revert-hook #'pdf-view-set-theme-background nil t))
|
|
|
|
|
(t
|
|
|
|
|
(remove-hook 'after-save-hook #'pdf-view-set-theme-background t)
|
|
|
|
|
(remove-hook 'after-revert-hook #'pdf-view-set-theme-background t)
|
|
|
|
|
(pdf-info-setoptions :render/usecolors 0)))
|
|
|
|
|
(pdf-view-refresh-themed-buffer pdf-view-themed-minor-mode))
|
|
|
|
|
|
|
|
|
|
(when pdf-view-use-unicode-ligther
|
|
|
|
|
;; This check uses an implementation detail, which hopefully gets the
|
|
|
|
|
;; right answer.
|
|
|
|
|
(and (fontp (char-displayable-p ?⎙))
|
|
|
|
|
(setcdr (assq 'pdf-view-printer-minor-mode minor-mode-alist)
|
|
|
|
|
(list " ⎙" )))
|
|
|
|
|
(and (fontp (char-displayable-p ?🌙))
|
|
|
|
|
(setcdr (assq 'pdf-view-midnight-minor-mode minor-mode-alist)
|
|
|
|
|
(list " 🌙" ))))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;; * ================================================================== *
|
|
|
|
|
;; * Hotspot handling
|
|
|
|
|
;; * ================================================================== *
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-add-hotspot-function (fn &optional layer)
|
|
|
|
|
"Register FN as a hotspot function in the current buffer, using LAYER.
|
|
|
|
|
|
|
|
|
|
FN will be called in the PDF buffer with the page-number and the
|
|
|
|
|
image size \(WIDTH . HEIGHT\) as arguments. It should return a
|
|
|
|
|
list of hotspots applicable to the the :map image-property.
|
|
|
|
|
|
|
|
|
|
LAYER determines the order: Functions in a higher LAYER will
|
|
|
|
|
supersede hotspots in lower ones."
|
|
|
|
|
(push (cons (or layer 0) fn)
|
|
|
|
|
pdf-view--hotspot-functions))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-remove-hotspot-function (fn)
|
|
|
|
|
"Unregister FN as a hotspot function in the current buffer."
|
|
|
|
|
(setq pdf-view--hotspot-functions
|
|
|
|
|
(cl-remove fn pdf-view--hotspot-functions
|
|
|
|
|
:key 'cdr)))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-sorted-hotspot-functions ()
|
|
|
|
|
;; TODO: write documentation!
|
|
|
|
|
(mapcar 'cdr (cl-sort (copy-sequence pdf-view--hotspot-functions)
|
|
|
|
|
'> :key 'car)))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-apply-hotspot-functions (window page image-size)
|
|
|
|
|
;; TODO: write documentation!
|
|
|
|
|
(unless pdf-view-inhibit-hotspots
|
|
|
|
|
(save-selected-window
|
|
|
|
|
(when window (select-window window 'norecord))
|
|
|
|
|
(apply 'nconc
|
|
|
|
|
(mapcar (lambda (fn)
|
|
|
|
|
(funcall fn page image-size))
|
|
|
|
|
(pdf-view-sorted-hotspot-functions))))))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;; * ================================================================== *
|
|
|
|
|
;; * Region
|
|
|
|
|
;; * ================================================================== *
|
|
|
|
|
|
|
|
|
|
(defun pdf-view--push-mark ()
|
|
|
|
|
;; TODO: write documentation!
|
|
|
|
|
(let (mark-ring)
|
|
|
|
|
(push-mark-command nil))
|
|
|
|
|
(setq deactivate-mark nil))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-active-region (&optional deactivate-p)
|
|
|
|
|
"Return the active region, a list of edges.
|
|
|
|
|
|
|
|
|
|
Deactivate the region if DEACTIVATE-P is non-nil."
|
|
|
|
|
(pdf-view-assert-active-region)
|
|
|
|
|
(prog1
|
|
|
|
|
pdf-view-active-region
|
|
|
|
|
(when deactivate-p
|
|
|
|
|
(pdf-view-deactivate-region))))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-deactivate-region ()
|
|
|
|
|
"Deactivate the region."
|
|
|
|
|
(interactive)
|
|
|
|
|
(when pdf-view-active-region
|
|
|
|
|
(setq pdf-view-active-region nil)
|
|
|
|
|
(deactivate-mark)
|
|
|
|
|
(pdf-view-redisplay t)))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-mouse-set-region (event &optional allow-extend-p
|
|
|
|
|
rectangle-p
|
|
|
|
|
selection-style)
|
|
|
|
|
"Select a region of text using the mouse with mouse event EVENT.
|
|
|
|
|
|
|
|
|
|
Allow for stacking of regions, if ALLOW-EXTEND-P is non-nil.
|
|
|
|
|
|
|
|
|
|
Create a rectangular region, if RECTANGLE-P is non-nil.
|
|
|
|
|
|
|
|
|
|
Overwrite `pdf-view-selection-style' with SELECTION-STYLE,
|
|
|
|
|
which is one of `glyph', `word', or `line'.
|
|
|
|
|
|
|
|
|
|
Stores the region in `pdf-view-active-region'."
|
|
|
|
|
(interactive "@e")
|
|
|
|
|
(setq pdf-view--have-rectangle-region rectangle-p)
|
|
|
|
|
(unless (and (eventp event)
|
|
|
|
|
(mouse-event-p event))
|
|
|
|
|
(signal 'wrong-type-argument (list 'mouse-event-p event)))
|
|
|
|
|
(unless (and allow-extend-p
|
|
|
|
|
(or (null (get this-command 'pdf-view-region-window))
|
|
|
|
|
(equal (get this-command 'pdf-view-region-window)
|
|
|
|
|
(selected-window))))
|
|
|
|
|
(pdf-view-deactivate-region))
|
|
|
|
|
(put this-command 'pdf-view-region-window
|
|
|
|
|
(selected-window))
|
|
|
|
|
(let* ((window (selected-window))
|
|
|
|
|
(pos (event-start event))
|
|
|
|
|
(begin-inside-image-p t)
|
|
|
|
|
(begin (if (posn-image pos)
|
|
|
|
|
(posn-object-x-y pos)
|
|
|
|
|
(setq begin-inside-image-p nil)
|
|
|
|
|
(posn-x-y pos)))
|
|
|
|
|
(abs-begin (posn-x-y pos))
|
|
|
|
|
(selection-style (or selection-style pdf-view-selection-style))
|
|
|
|
|
pdf-view-continuous
|
|
|
|
|
region)
|
|
|
|
|
(when (pdf-util-track-mouse-dragging (event 0.05)
|
|
|
|
|
(let* ((pos (event-start event))
|
|
|
|
|
(end (posn-object-x-y pos))
|
|
|
|
|
(end-inside-image-p
|
|
|
|
|
(and (eq window (posn-window pos))
|
|
|
|
|
(posn-image pos))))
|
|
|
|
|
(when (or end-inside-image-p
|
|
|
|
|
begin-inside-image-p)
|
|
|
|
|
(cond
|
|
|
|
|
((and end-inside-image-p
|
|
|
|
|
(not begin-inside-image-p))
|
|
|
|
|
;; Started selection outside the image, setup begin.
|
|
|
|
|
(let* ((xy (posn-x-y pos))
|
|
|
|
|
(dxy (cons (- (car xy) (car begin))
|
|
|
|
|
(- (cdr xy) (cdr begin))))
|
|
|
|
|
(size (pdf-view-image-size t)))
|
|
|
|
|
(setq begin (cons (max 0 (min (car size)
|
|
|
|
|
(- (car end) (car dxy))))
|
|
|
|
|
(max 0 (min (cdr size)
|
|
|
|
|
(- (cdr end) (cdr dxy)))))
|
|
|
|
|
;; Store absolute position for later.
|
|
|
|
|
abs-begin (cons (- (car xy)
|
|
|
|
|
(- (car end)
|
|
|
|
|
(car begin)))
|
|
|
|
|
(- (cdr xy)
|
|
|
|
|
(- (cdr end)
|
|
|
|
|
(cdr begin))))
|
|
|
|
|
begin-inside-image-p t)))
|
|
|
|
|
((and begin-inside-image-p
|
|
|
|
|
(not end-inside-image-p))
|
|
|
|
|
;; Moved outside the image, setup end.
|
|
|
|
|
(let* ((xy (posn-x-y pos))
|
|
|
|
|
(dxy (cons (- (car xy) (car abs-begin))
|
|
|
|
|
(- (cdr xy) (cdr abs-begin))))
|
|
|
|
|
(size (pdf-view-image-size t)))
|
|
|
|
|
(setq end (cons (max 0 (min (car size)
|
|
|
|
|
(+ (car begin) (car dxy))))
|
|
|
|
|
(max 0 (min (cdr size)
|
|
|
|
|
(+ (cdr begin) (cdr dxy)))))))))
|
|
|
|
|
(let ((iregion (if rectangle-p
|
|
|
|
|
(list (min (car begin) (car end))
|
|
|
|
|
(min (cdr begin) (cdr end))
|
|
|
|
|
(max (car begin) (car end))
|
|
|
|
|
(max (cdr begin) (cdr end)))
|
|
|
|
|
(list (car begin) (cdr begin)
|
|
|
|
|
(car end) (cdr end)))))
|
|
|
|
|
(setq region
|
|
|
|
|
(pdf-util-scale-pixel-to-relative iregion))
|
|
|
|
|
(pdf-view-display-region
|
|
|
|
|
(cons region pdf-view-active-region)
|
|
|
|
|
rectangle-p
|
|
|
|
|
selection-style)
|
|
|
|
|
(pdf-util-scroll-to-edges iregion)))))
|
|
|
|
|
(setq pdf-view-active-region
|
|
|
|
|
(append pdf-view-active-region
|
|
|
|
|
(list region)))
|
|
|
|
|
(pdf-view--push-mark))))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-mouse-extend-region (event)
|
|
|
|
|
"Extend the currently active region with mouse event EVENT."
|
|
|
|
|
(interactive "@e")
|
|
|
|
|
(pdf-view-mouse-set-region
|
|
|
|
|
event t pdf-view--have-rectangle-region))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-mouse-set-region-rectangle (event)
|
|
|
|
|
"Like `pdf-view-mouse-set-region' but displays as a rectangle.
|
|
|
|
|
|
|
|
|
|
EVENT is the mouse event.
|
|
|
|
|
|
|
|
|
|
This is more useful for commands like
|
|
|
|
|
`pdf-view-extract-region-image'."
|
|
|
|
|
(interactive "@e")
|
|
|
|
|
(pdf-view-mouse-set-region event nil t))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-display-region (&optional region rectangle-p selection-style)
|
|
|
|
|
;; TODO: write documentation!
|
|
|
|
|
(unless region
|
|
|
|
|
(pdf-view-assert-active-region)
|
|
|
|
|
(setq region pdf-view-active-region))
|
|
|
|
|
(let ((colors (pdf-util-face-colors
|
|
|
|
|
(if rectangle-p 'pdf-view-rectangle 'pdf-view-region)
|
|
|
|
|
(bound-and-true-p pdf-view-dark-minor-mode)))
|
|
|
|
|
(page (pdf-view-current-page))
|
|
|
|
|
(width (car (pdf-view-image-size))))
|
|
|
|
|
(pdf-view-display-image
|
|
|
|
|
(pdf-view-create-image
|
|
|
|
|
(if rectangle-p
|
|
|
|
|
(pdf-info-renderpage-highlight
|
|
|
|
|
page width nil
|
|
|
|
|
`(,(car colors) ,(cdr colors) 0.35 ,@region))
|
|
|
|
|
(pdf-info-renderpage-text-regions
|
|
|
|
|
page width nil selection-style nil
|
|
|
|
|
`(,(car colors) ,(cdr colors) ,@region)))
|
|
|
|
|
:width width))))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-kill-ring-save ()
|
|
|
|
|
"Copy the region to the `kill-ring'."
|
|
|
|
|
(interactive)
|
|
|
|
|
(pdf-view-assert-active-region)
|
|
|
|
|
(let* ((txt (pdf-view-active-region-text)))
|
|
|
|
|
(pdf-view-deactivate-region)
|
|
|
|
|
(kill-new (mapconcat 'identity txt "\n"))))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-mark-whole-page ()
|
|
|
|
|
"Mark the whole page."
|
|
|
|
|
(interactive)
|
|
|
|
|
(pdf-view-deactivate-region)
|
|
|
|
|
(setq pdf-view-active-region
|
|
|
|
|
(list (list 0 0 1 1)))
|
|
|
|
|
(pdf-view--push-mark)
|
|
|
|
|
(pdf-view-display-region))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-active-region-text ()
|
|
|
|
|
"Return the text of the active region as a list of strings."
|
|
|
|
|
(pdf-view-assert-active-region)
|
|
|
|
|
(mapcar
|
|
|
|
|
(lambda (edges)
|
|
|
|
|
(pdf-info-gettext
|
|
|
|
|
(pdf-view-current-page)
|
|
|
|
|
edges
|
|
|
|
|
pdf-view-selection-style))
|
|
|
|
|
pdf-view-active-region))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-extract-region-image (regions &optional page size
|
|
|
|
|
output-buffer no-display-p)
|
|
|
|
|
;; TODO: what is "resp."? Avoid contractions.
|
|
|
|
|
"Create a PNG image of REGIONS.
|
|
|
|
|
|
|
|
|
|
REGIONS should have the same form as `pdf-view-active-region',
|
|
|
|
|
which see. PAGE and SIZE are the page resp. base-size of the
|
|
|
|
|
image from which the image-regions will be created; they default
|
|
|
|
|
to `pdf-view-current-page' resp. `pdf-view-image-size'.
|
|
|
|
|
|
|
|
|
|
Put the image in OUTPUT-BUFFER, defaulting to \"*PDF region
|
|
|
|
|
image*\" and display it, unless NO-DISPLAY-P is non-nil.
|
|
|
|
|
|
|
|
|
|
In case of multiple regions, the resulting image is constructed
|
|
|
|
|
by joining them horizontally. For this operation (and this only)
|
|
|
|
|
the `convert' program is used."
|
|
|
|
|
|
|
|
|
|
(interactive
|
|
|
|
|
(list (if (pdf-view-active-region-p)
|
|
|
|
|
(pdf-view-active-region t)
|
|
|
|
|
'((0 0 1 1)))))
|
|
|
|
|
(unless page
|
|
|
|
|
(setq page (pdf-view-current-page)))
|
|
|
|
|
(unless size
|
|
|
|
|
(setq size (pdf-view-image-size)))
|
|
|
|
|
(unless output-buffer
|
|
|
|
|
(setq output-buffer (get-buffer-create "*PDF image*")))
|
|
|
|
|
(let* ((images (mapcar (lambda (edges)
|
|
|
|
|
(let ((file (make-temp-file "pdf-view"))
|
|
|
|
|
(coding-system-for-write 'binary))
|
|
|
|
|
(write-region
|
|
|
|
|
(pdf-info-renderpage
|
|
|
|
|
page (car size)
|
|
|
|
|
:crop-to edges)
|
|
|
|
|
nil file nil 'no-message)
|
|
|
|
|
file))
|
|
|
|
|
regions))
|
|
|
|
|
result)
|
|
|
|
|
(unwind-protect
|
|
|
|
|
(progn
|
|
|
|
|
(if (= (length images) 1)
|
|
|
|
|
(setq result (car images))
|
|
|
|
|
(setq result (make-temp-file "pdf-view"))
|
|
|
|
|
;; Join the images horizontally with a gap of 10 pixel.
|
|
|
|
|
(pdf-util-convert
|
|
|
|
|
"-noop" ;; workaround limitations of this function
|
|
|
|
|
result
|
|
|
|
|
:commands `("("
|
|
|
|
|
,@images
|
|
|
|
|
"-background" "white"
|
|
|
|
|
"-splice" "0x10+0+0"
|
|
|
|
|
")"
|
|
|
|
|
"-gravity" "Center"
|
|
|
|
|
"-append"
|
|
|
|
|
"+gravity"
|
|
|
|
|
"-chop" "0x10+0+0")
|
|
|
|
|
:apply '((0 0 0 0))))
|
|
|
|
|
(with-current-buffer output-buffer
|
|
|
|
|
(let ((inhibit-read-only t))
|
|
|
|
|
(erase-buffer))
|
|
|
|
|
(set-buffer-multibyte nil)
|
|
|
|
|
(insert-file-contents-literally result)
|
|
|
|
|
(image-mode)
|
|
|
|
|
(unless no-display-p
|
|
|
|
|
(pop-to-buffer (current-buffer)))))
|
|
|
|
|
(dolist (f (cons result images))
|
|
|
|
|
(when (file-exists-p f)
|
|
|
|
|
(delete-file f))))))
|
2023-08-10 14:03:04 +00:00
|
|
|
|
|
|
|
|
|
(defun pdf-view-set-selection-style (&optional style)
|
|
|
|
|
"Set `pdf-view-selection-style' to STYLE in the current buffer.
|
|
|
|
|
|
|
|
|
|
When called interactively or without an argument, cycle between
|
|
|
|
|
the selection styles."
|
|
|
|
|
(interactive)
|
|
|
|
|
(unless style
|
|
|
|
|
(setq style (or (cadr (memq pdf-view-selection-style '(glyph word line)))
|
|
|
|
|
'glyph))
|
|
|
|
|
(message "Setting selection style to `%s'." style))
|
|
|
|
|
(pdf-view-deactivate-region)
|
|
|
|
|
(setq-local pdf-view-selection-style style))
|
|
|
|
|
|
2023-02-21 18:17:08 +00:00
|
|
|
|
|
|
|
|
|
;; * ================================================================== *
|
|
|
|
|
;; * Bookmark + Register Integration
|
|
|
|
|
;; * ================================================================== *
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-bookmark-make-record (&optional no-page no-slice no-size no-origin)
|
|
|
|
|
;; TODO: add NO-PAGE, NO-SLICE, NO-SIZE, NO-ORIGIN to the docstring.
|
|
|
|
|
"Create a bookmark PDF record.
|
|
|
|
|
|
|
|
|
|
The optional, boolean args exclude certain attributes."
|
|
|
|
|
(let ((displayed-p (eq (current-buffer)
|
|
|
|
|
(window-buffer))))
|
|
|
|
|
(cons (buffer-name)
|
|
|
|
|
(append (bookmark-make-record-default nil t 1)
|
|
|
|
|
`(,(unless no-page
|
|
|
|
|
(cons 'page (pdf-view-current-page)))
|
|
|
|
|
,(unless no-slice
|
|
|
|
|
(cons 'slice (and displayed-p
|
|
|
|
|
(pdf-view-current-slice))))
|
|
|
|
|
,(unless no-size
|
|
|
|
|
(cons 'size pdf-view-display-size))
|
|
|
|
|
,(unless no-origin
|
|
|
|
|
(cons 'origin
|
|
|
|
|
(and displayed-p
|
|
|
|
|
(let ((edges (pdf-util-image-displayed-edges nil t)))
|
|
|
|
|
(pdf-util-scale-pixel-to-relative
|
|
|
|
|
(cons (car edges) (cadr edges)) nil t)))))
|
|
|
|
|
(handler . pdf-view-bookmark-jump-handler))))))
|
|
|
|
|
|
|
|
|
|
;;;###autoload
|
|
|
|
|
(defun pdf-view-bookmark-jump-handler (bmk)
|
|
|
|
|
"The bookmark handler-function interface for bookmark BMK.
|
|
|
|
|
|
|
|
|
|
See also `pdf-view-bookmark-make-record'."
|
|
|
|
|
(let ((page (bookmark-prop-get bmk 'page))
|
|
|
|
|
(slice (bookmark-prop-get bmk 'slice))
|
|
|
|
|
(size (bookmark-prop-get bmk 'size))
|
|
|
|
|
(origin (bookmark-prop-get bmk 'origin))
|
|
|
|
|
(file (bookmark-prop-get bmk 'filename))
|
|
|
|
|
(show-fn-sym (make-symbol "pdf-view-bookmark-after-jump-hook")))
|
|
|
|
|
(fset show-fn-sym
|
|
|
|
|
(lambda ()
|
|
|
|
|
(remove-hook 'bookmark-after-jump-hook show-fn-sym)
|
|
|
|
|
(unless (derived-mode-p 'pdf-view-mode)
|
|
|
|
|
(pdf-view-mode))
|
|
|
|
|
(with-selected-window
|
|
|
|
|
(or (get-buffer-window (current-buffer) 0)
|
|
|
|
|
(selected-window))
|
|
|
|
|
(when size
|
|
|
|
|
(setq-local pdf-view-display-size size))
|
|
|
|
|
(when slice
|
|
|
|
|
(apply 'pdf-view-set-slice slice))
|
|
|
|
|
(when (numberp page)
|
|
|
|
|
(pdf-view-goto-page page))
|
|
|
|
|
(when origin
|
|
|
|
|
(let ((size (pdf-view-image-size t)))
|
|
|
|
|
(image-set-window-hscroll
|
|
|
|
|
(round (/ (* (car origin) (car size))
|
|
|
|
|
(frame-char-width))))
|
|
|
|
|
(image-set-window-vscroll
|
|
|
|
|
(round (/ (* (cdr origin) (cdr size))
|
|
|
|
|
(if pdf-view-have-image-mode-pixel-vscroll
|
|
|
|
|
1
|
|
|
|
|
(frame-char-height))))))))))
|
|
|
|
|
(add-hook 'bookmark-after-jump-hook show-fn-sym)
|
|
|
|
|
(set-buffer (or (find-buffer-visiting file)
|
|
|
|
|
(find-file-noselect file)))))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-bookmark-jump (bmk)
|
|
|
|
|
"Switch to bookmark BMK.
|
|
|
|
|
|
|
|
|
|
This function is like `bookmark-jump', but it always uses the
|
|
|
|
|
selected window for display and does not run any hooks. Also, it
|
|
|
|
|
works only with bookmarks created by
|
|
|
|
|
`pdf-view-bookmark-make-record'."
|
|
|
|
|
|
|
|
|
|
(let* ((file (bookmark-prop-get bmk 'filename))
|
|
|
|
|
(buffer (or (find-buffer-visiting file)
|
|
|
|
|
(find-file-noselect file))))
|
|
|
|
|
(switch-to-buffer buffer)
|
|
|
|
|
(let (bookmark-after-jump-hook)
|
|
|
|
|
(pdf-view-bookmark-jump-handler bmk)
|
|
|
|
|
(run-hooks 'bookmark-after-jump-hook))))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-registerv-make ()
|
|
|
|
|
"Create a PDF register entry of the current position."
|
|
|
|
|
(registerv-make
|
|
|
|
|
(pdf-view-bookmark-make-record nil t t)
|
|
|
|
|
:print-func 'pdf-view-registerv-print-func
|
|
|
|
|
:jump-func 'pdf-view-bookmark-jump
|
|
|
|
|
:insert-func (lambda (bmk)
|
|
|
|
|
(insert (format "%S" bmk)))))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-registerv-print-func (bmk)
|
|
|
|
|
"Print a textual representation of bookmark BMK.
|
|
|
|
|
|
|
|
|
|
This function is used as the `:print-func' property with
|
|
|
|
|
`registerv-make'."
|
|
|
|
|
(let* ((file (bookmark-prop-get bmk 'filename))
|
|
|
|
|
(buffer (find-buffer-visiting file))
|
|
|
|
|
(page (bookmark-prop-get bmk 'page))
|
|
|
|
|
(origin (bookmark-prop-get bmk 'origin)))
|
|
|
|
|
(princ (format "PDF position: %s, page %d, %d%%"
|
|
|
|
|
(if buffer
|
|
|
|
|
(buffer-name buffer)
|
|
|
|
|
file)
|
|
|
|
|
(or page 1)
|
|
|
|
|
(if origin
|
|
|
|
|
(round (* 100 (cdr origin)))
|
|
|
|
|
0)))))
|
|
|
|
|
|
|
|
|
|
(defmacro pdf-view-with-register-alist (&rest body)
|
|
|
|
|
"Setup the proper binding for `register-alist' in BODY.
|
|
|
|
|
|
|
|
|
|
This macro may not work as desired when it is nested. See also
|
|
|
|
|
`pdf-view-use-dedicated-register'."
|
|
|
|
|
(declare (debug t) (indent 0))
|
|
|
|
|
(let ((dedicated-p (make-symbol "dedicated-p")))
|
|
|
|
|
`(let* ((,dedicated-p pdf-view-use-dedicated-register)
|
|
|
|
|
(register-alist
|
|
|
|
|
(if ,dedicated-p
|
|
|
|
|
pdf-view-register-alist
|
|
|
|
|
register-alist)))
|
|
|
|
|
(unwind-protect
|
|
|
|
|
(progn ,@body)
|
|
|
|
|
(when ,dedicated-p
|
|
|
|
|
(setq pdf-view-register-alist register-alist))))))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-position-to-register (register)
|
|
|
|
|
"Store current PDF position in register REGISTER.
|
|
|
|
|
|
|
|
|
|
See also `point-to-register'."
|
|
|
|
|
(interactive
|
|
|
|
|
(list (pdf-view-with-register-alist
|
|
|
|
|
(register-read-with-preview "Position to register: "))))
|
|
|
|
|
(pdf-view-with-register-alist
|
|
|
|
|
(set-register register (pdf-view-registerv-make))))
|
|
|
|
|
|
|
|
|
|
(defun pdf-view-jump-to-register (register &optional delete return-register)
|
|
|
|
|
;; TODO: add RETURN-REGISTER to the docstring.
|
|
|
|
|
"Move point to a position stored in a REGISTER.
|
|
|
|
|
|
|
|
|
|
Optional parameter DELETE is defined as in `jump-to-register'."
|
|
|
|
|
(interactive
|
|
|
|
|
(pdf-view-with-register-alist
|
|
|
|
|
(list
|
|
|
|
|
(register-read-with-preview "Jump to register: ")
|
|
|
|
|
current-prefix-arg
|
|
|
|
|
(and (or pdf-view-use-dedicated-register
|
|
|
|
|
(local-variable-p 'register-alist))
|
|
|
|
|
(characterp last-command-event)
|
|
|
|
|
last-command-event))))
|
|
|
|
|
(pdf-view-with-register-alist
|
|
|
|
|
(let ((return-pos (and return-register
|
|
|
|
|
(pdf-view-registerv-make))))
|
|
|
|
|
(jump-to-register register delete)
|
|
|
|
|
(when return-register
|
|
|
|
|
(set-register return-register return-pos)))))
|
|
|
|
|
|
|
|
|
|
(provide 'pdf-view)
|
|
|
|
|
|
|
|
|
|
;;; pdf-view.el ends here
|