diff options
| author | Craig Jennings <c@cjennings.net> | 2025-10-12 11:47:26 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2025-10-12 11:47:26 -0500 |
| commit | 092304d9e0ccc37cc0ddaa9b136457e56a1cac20 (patch) | |
| tree | ea81999b8442246c978b364dd90e8c752af50db5 /custom/pdf-continuous-scroll-mode-latest.el | |
changing repositories
Diffstat (limited to 'custom/pdf-continuous-scroll-mode-latest.el')
| -rw-r--r-- | custom/pdf-continuous-scroll-mode-latest.el | 1046 |
1 files changed, 1046 insertions, 0 deletions
diff --git a/custom/pdf-continuous-scroll-mode-latest.el b/custom/pdf-continuous-scroll-mode-latest.el new file mode 100644 index 00000000..b05890c4 --- /dev/null +++ b/custom/pdf-continuous-scroll-mode-latest.el @@ -0,0 +1,1046 @@ +;;; pdf-continuous-scroll-mode.el --- Continuous scroll for pdf-tools -*- lexical-binding: t; -*- + +;; Copyright (C) 2020 Daniel Laurens Nicolai + +;; Author: Daniel Laurens Nicolai <dalanicolai@gmail.com> +;; Version: 0 +;; Keywords: pdf-tools, +;; Package-Requires: ((emacs "27.1") (pdf-tools "1.0")) +;; URL: https://github.com/dalanicolai/pdf-continuous-scroll-mode.el + + +;; 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 <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Usage: + +;;; Code: +(require 'pdf-tools) +(require 'pdf-annot) +(require 'image-mode) +(require 'svg) +(require 'cl-lib) + +(defgroup book nil + "Bookroll customizations.") + +(defcustom book-scroll-fraction 32 + "The scroll step size in 1/fraction of page." + :type 'integer) + +(defcustom book-page-vertical-margin 5 + "The size of the vertical margins around a page." + :type 'integer) + +(defcustom book-reverse-scrolling nil + "Reverse default scrolling direction" + :group 'pdf-continuous-scroll + :type 'boolean) + +(defvar-local book-number-of-pages 0) +(defvar-local book-contents-end-pos 0) + +;; (defmacro book-current-page (&optional win) +;; `(image-mode-window-get 'page ,win)) +(defmacro book-overlays (&optional window) `(image-mode-window-get 'overlays ,window)) +(defmacro book-image-sizes (&optional window) `(image-mode-window-get 'image-sizes ,window)) +(defmacro book-image-positions (&optional window) `(image-mode-window-get 'image-positions ,window)) +(defmacro book-currently-displayed-pages (&optional window) `(image-mode-window-get 'displayed-pages ,window)) + +;; the following function only exists for backward compatibility +(defun pdf-continuous-scroll-mode () + (print "This is a new version of pdf-continuous-scroll-mode.el +Despite the name, the `pdf-continuous-scroll-mode' itself has +been removed. You should make sure that you remove the function +from the pdf-view-mode-hook. If you prefer to use the previous +'2-buffer' version, then you can download and load the file from +the previous commit at +https://github.com/dalanicolai/pdf-continuous-scroll-mode.el/tree/615dcfbf7a9b2ff602a39da189e5eb766600047f.")) + +(make-obsolete 'pdf-continuous-scroll-mode nil "3 February 2022") + +(defun image-mode-winprops (&optional window cleanup) + "Return winprops of WINDOW. +A winprops object has the shape (WINDOW . ALIST). +WINDOW defaults to `selected-window' if it displays the current buffer, and +otherwise it defaults to t, used for times when the buffer is not displayed." + (cond ((null window) + (setq window + (if (eq (current-buffer) (window-buffer)) (selected-window) t))) + ((eq window t)) + ((not (windowp window)) + (error "Not a window: %s" window))) + (when-let (o (nth 272 (assq 'overlays (assq window image-mode-winprops-alist)))) + (message "%s" (image-property (overlay-get o 'display) :type))) + (when cleanup + (setq image-mode-winprops-alist + (delq nil (mapcar (lambda (winprop) + (let ((w (car-safe winprop))) + (if (or (not (windowp w)) (window-live-p w)) + winprop))) + image-mode-winprops-alist)))) + (let ((winprops (assq window image-mode-winprops-alist))) + ;; For new windows, set defaults from the latest. + (if winprops + ;; Move window to front. + (setq image-mode-winprops-alist + (cons winprops (delq winprops image-mode-winprops-alist))) + (setq winprops (cons window + (copy-alist (cdar image-mode-winprops-alist)))) + ;; Add winprops before running the hook, to avoid inf-loops if the hook + ;; triggers window-configuration-change-hook. + (setq image-mode-winprops-alist + (cons winprops image-mode-winprops-alist)) + (run-hook-with-args 'image-mode-new-window-functions winprops)) + winprops)) + +;; We overwrite the following image-mode function to make it also +;; reapply winprops when the overlay has the 'invisible property +(defun image-get-display-property () + (or (get-char-property (point-min) 'display + ;; There might be different images for different displays. + (if (eq (window-buffer) (current-buffer)) + (selected-window))) + (get-char-property (point-min) 'invisible + ;; There might be different images for different displays. + (if (eq (window-buffer) (current-buffer)) + (selected-window))))) + +(defun image-set-window-vscroll (vscroll) + (setf (image-mode-window-get 'vscroll) vscroll + (image-mode-window-get 'relative-vscroll) (/ (float vscroll) + (car (last (book-image-positions))))) + (set-window-vscroll (selected-window) vscroll t)) + +(defun image-mode-reapply-winprops () + ;; When set-window-buffer, set hscroll and vscroll to what they were + ;; last time the image was displayed in this window. + (when (listp image-mode-winprops-alist) + ;; Beware: this call to image-mode-winprops can't be optimized away, + ;; because it not only gets the winprops data but sets it up if needed + ;; (e.g. it's used by doc-view to display the image in a new window). + (let* ((winprops (image-mode-winprops nil t)) + (hscroll (image-mode-window-get 'hscroll winprops)) + (vscroll (round (* (image-mode-window-get 'relative-vscroll winprops) + (car (last (book-image-positions))))))) + (when (image-get-display-property) ;Only do it if we display an image! + (if hscroll (set-window-hscroll (selected-window) hscroll)) + (if vscroll (set-window-vscroll (selected-window) vscroll t)))))) + +(defun book-create-image-positions (image-sizes) + (let ((sum 0) + (positions (list 0))) + (dolist (s image-sizes) + ;; the margin is added on both sides + (setq sum (+ sum (cdr s) (* 2 (or book-page-vertical-margin pdf-view-image-relief)))) + (push sum positions)) + (nreverse positions))) + +(defun book-create-overlays-list (winprops) + "Create list of overlays spread out over the buffer contents. +Pass non-nil value for include-first when the buffer text starts with a match." + ;; first overlay starts at 1 + ;; (setq book-contents-end-pos (goto-char (point-max))) + (goto-char book-contents-end-pos) + (let ((eobp (eobp)) + overlays) + (if (eobp) + (insert " ") + (forward-char)) + (push (make-overlay (1- (point)) (point)) overlays) + (let ((overlays-list (dotimes (_ (1- (length (book-image-sizes))) (nreverse overlays)) + (if eobp + (insert "\n ") + (forward-char 2)) + (push (make-overlay (1- (point)) (point)) overlays)))) + ;; all windows require their overlays to apply to the window only because + ;; the windows and therefore bookroll's might have different sizes (see + ;; `overlay-properties') + (mapc (lambda (o) (overlay-put o 'window (car winprops))) overlays-list) + (image-mode-window-put 'overlays overlays-list winprops))) + (goto-char (point-min)) + (set-buffer-modified-p nil)) + +(defun book-create-empty-page (size) + (pcase-let* ((`(,w . ,h) size)) + (svg-image (svg-create w h) + :margin (cons 0 book-page-vertical-margin)))) + +(defun book-create-placeholders () + (let* ((constant-size (cl-every #'eql (book-image-sizes) (cdr (book-image-sizes)))) + (ph (when constant-size (book-create-empty-page (car (book-image-sizes)))))) + (dotimes (i (length (book-image-sizes))) + ;; (let ((p (1+ i)));; shift by 1 to match with page numbers + ;; (overlay-put (nth i overlays-list) 'display (or ph (book-create-empty-page (nth i (book-image-sizes)))))))) + (overlay-put (nth i (book-overlays)) 'display (or ph (book-create-empty-page (nth i (book-image-sizes)))))))) + +(defun book-current-page () + (interactive) + (let ((i 0) + (cur-pos (window-vscroll nil t))) + (while (<= (nth (1+ i) (book-image-positions)) (+ cur-pos (/ (window-pixel-height) 2))) + (setq i (1+ i))) + (1+ i))) + +(defun book-page-triplet (page) + ;; first handle the cases when the doc has only one or two pages + (pcase (pdf-info-number-of-pages) + (1 '(1)) + (2 '(1 2)) + (_ (pcase page + (1 '(1 2)) + ((pred (= book-number-of-pages)) (list page (- page 1))) + (p (list (- p 1) p (+ p 1))))))) + +(defun book-remove-page-image (page) + ;; remove page image means insert back empty image (placeholder) + (overlay-put (nth (1- page) (book-overlays)) + 'display + (book-create-empty-page (nth (1- page) (book-image-sizes))))) + + +(defun book-scroll-to-page (page) + (interactive "n") + ;; (book-update-page-triplet page) + (let* ((elt (1- page))) + (set-window-vscroll nil + (+ (nth elt (book-image-positions)) + (or book-page-vertical-margin pdf-view-image-relief)) + t))) + +(defvar pdf-continuous-suppress-introduction nil) + +(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)) + + ;; 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) + ;; In Emacs >= 24.4, `cua-copy-region' should have been advised when + ;; loading pdf-view.el so as to make it work with + ;; pdf-view-mode. Disable cua-mode if that is not the case. + ;; FIXME: cua-mode is a global minor-mode, but setting cua-mode to + ;; nil seems to do the trick. + (when (and (bound-and-true-p cua-mode) + (version< emacs-version "24.4")) + (setq-local cua-mode nil)) + + (setq-local book-contents-end-pos (point-max)) + (setq-local book-number-of-pages (pdf-cache-number-of-pages)) + + (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) + + (unless pdf-continuous-suppress-introduction + (pdf-continuous-introduce)) + + ;; 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) + + (unless pdf-continuous-suppress-introduction + (switch-to-buffer "*pdf-continuous-introduction*")))) + (current-buffer))) + +(defun pdf-continuous-toggle-message () + (interactive) + (setq pdf-continuous-suppress-introduction + (if pdf-continuous-suppress-introduction + nil + (message "pdf-continuous message suppressed") + t))) + +(defun pdf-continuous-introduce () + (with-current-buffer (get-buffer-create "*pdf-continuous-introduction*") + (insert "NEW PDF CONTINUOUS SCROLL: INTRODUCTION 3 February 2022 + +Welcome to the new pdf-continuous-scroll-mode, now finally +providing continuous scroll in a single buffer. 🎉🍾 + +Despite the name, the `pdf-continuous-scroll-mode' itself has +been removed. You should make sure that you remove the function +from the pdf-view-mode-hook. If you prefer to use the previous +'2-buffer' version, then you can download and load the file from +the previous commit at +https://github.com/dalanicolai/pdf-continuous-scroll-mode.el/tree/615dcfbf7a9b2ff602a39da189e5eb766600047f. + +My apologies for this rude interruption, however this behavior is +only temporary (until this functionality gets merged into +pdf-tools in May or so). + +There are two reasons for this interruption: + +- second, to inform you about how to obtain or set indicators + allowing you to differentiate between the pages. The default + design uses the customizable `book-page-vertical-margin' + variable, which sets vertical margins for the page images. If + your Emacs theme has a different background color than your + books page color, this will nicely indicate the page + 'transitions'. However, if the background color and the page + color are the same, then you can set the + `book-page-vertical-margin' to 0 and instead set + `pdf-view-image-relief' to some non negative number to help you + differentiate between the pages. + + Also, redisplay (e.g. splitting buffers), does not work + flawlessly yet, but you can simply split and start scrolling, + or use `M-x pdf-view-goto-page', in the buffers and the display + problem will 'fix itself'. (To fix the root of the problem, I + probably have to make `image-mode-reapply-winprops' use + 'relative' instead of `absolute' vscroll. Also, I have noticed, + that, while in Spacemacs redisplay works almost fine, in + vanilla Emacs the vscroll does not get restored despite + `image-mode-reapply-winprops' getting called.) + +- first, I would like to inform you that I would be very happy + with any small donation if you can afford it (I guess most + Emacs PDF-tools users are students). Despite the low number of + lines of code here (of which a large part is adapted from + pdf-view.el), creating this package has cost me a lot of + effort. I think writing the code has only cost me about 0.001 + percent of the time, while most of the time has been invested + in investigating pdf-tools and doc-view and their very opaque + and non-trivial display mechanisms. + + You can find donate buttons on the + pdf-continuous-scroll-mode.el github page + (https://github.com/dalanicolai/pdf-continuous-scroll-mode.el). + + With the help of donations I could work on fixing bugs and on + improving pdf-tools and image-mode documentation (which are + really great packages, but they lack documentation. If about 50 + lines of documentation had been available, it would probably + have saved me two/three weeks of work). + + Besides that, I have also written an alternative pdf + server (https://github.com/vedang/pdf-tools/pull/61) using + python with the excellent pymupdf + package (https://pymupdf.readthedocs.io/en/latest/). Again in + that case, writing the code was only a tiny fraction of the + total work. This alternative server provides new kinds of + annotation functionality like line, arrow and free text + annotations. Furthermore, it offers the possibility to send + python code directly to the server, so that it is possible to + use the full features provided by pymupdf. + + The combined work has cost me almost two months of almost full + time investigating, debugging, iterating. + + To save you a long interesting story, I just mention that + currently I am dependent on others for paying my rent and my + food. Otherwise, I would have been even more happy by + sharing/donating this package without asking for any donations. + + I have created a few more packages that can be found and are + described at my github profile + page (https://github.com/dalanicolai). + +You can toggle off this message by doing `M-x +pdf-continuous-toggle-message' or setting +`pdf-continuous-suppress-introduction' to non-nil in your +dotfile. Or you can simply close this buffer and start reading. + +Thank you. +Daniel + +Happy scrolling!" +) + (goto-char (point-min)))) + + +;; Despite what its docstring says, this function does not go to page in all +;; `pdf-view' windows when WINDOW is non-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)))) + (image-set-window-vscroll (+ (nth (1- page) (book-image-positions)) + (or book-page-vertical-margin pdf-view-image-relief))) + nil) + +(defun pdf-continuous-scroll-forward (&optional pixels) + ;; (defun pdf-view-next-line-or-next-page () + (interactive) + ;; because pages could have different heights, we calculate the step size on each scroll + ;; TODO define constant scroll size if doc has single page height + (let* ((scroll-step-size (/ (cdr (pdf-view-image-size)) book-scroll-fraction)) + (page-end (nth (pdf-view-current-page) (book-image-positions))) + (vscroll (window-vscroll nil t)) + (new-vscroll (image-set-window-vscroll (if (< vscroll (- (car (last (book-image-positions))) + (window-pixel-height))) + (+ vscroll (or pixels scroll-step-size)) + (message "End of book") + vscroll)))) + (when (> (+ new-vscroll (/ (window-pixel-height) 2)) page-end) + (let ((old-page (pdf-view-current-page)) + (new-page (alist-get 'page (cl-incf (pdf-view-current-page))))) + (when (> old-page 1) + (book-remove-page-image (1- old-page)) + (setf (book-currently-displayed-pages) (delete (1- old-page) (book-currently-displayed-pages)))) + (when (< new-page (pdf-info-number-of-pages)) + (pdf-view-display-triplet new-page))))) + ;; :width doc-view-image-width + ;; :pointer 'arrow + ;; :margin (cons 0 book-page-vertical-margin)))))) + (sit-for 0)) + +(defun pdf-continuous-scroll-backward (&optional pixels) + ;; (defun pdf-view-next-line-or-next-page () + (interactive) + ;; because pages could have different heights, we calculate the step size on each scroll + ;; TODO define constant scroll size if doc has single page height + (let* ((scroll-step-size (/ (cdr (pdf-view-image-size)) book-scroll-fraction)) + (page-beg (nth (1- (pdf-view-current-page)) (book-image-positions))) + (new-vscroll (image-set-window-vscroll (- (window-vscroll nil t) (or pixels scroll-step-size))))) + (when (< (+ new-vscroll (/ (window-pixel-height) 2)) page-beg) + (let ((old-page (pdf-view-current-page)) + (new-page (alist-get 'page (cl-decf (pdf-view-current-page))))) + (when (< old-page (pdf-info-number-of-pages)) + (book-remove-page-image (1+ old-page)) + (setf (book-currently-displayed-pages) (delete (1+ old-page) (book-currently-displayed-pages)))) + (when (> new-page 1) + (pdf-view-display-triplet new-page))))) + ;; :width doc-view-image-width + ;; :pointer 'arrow + ;; :margin (cons 0 book-page-vertical-margin)))))) + (sit-for 0)) + +(defun pdf-cs-mouse-scroll-forward () + (interactive) + (with-selected-window + (or (caadr last-input-event) (selected-window)) + (if book-reverse-scrolling + (pdf-continuous-scroll-backward nil) + (pdf-continuous-scroll-forward nil)))) + +(defun pdf-cs-mouse-scroll-backward () + (interactive) + (with-selected-window + (or (caadr last-input-event) (selected-window)) + (if book-reverse-scrolling + (pdf-continuous-scroll-forward nil) + (pdf-continuous-scroll-backward nil)))) + +(defun pdf-continuous-next-page () + (interactive) + (pdf-continuous-scroll-forward (+ (cdr (nth (1- (book-current-page)) (book-image-sizes))) + (* 2 (or book-page-vertical-margin pdf-view-image-relief))))) + +(defun pdf-continuous-previous-page () + (interactive) + (pdf-continuous-scroll-backward (+ (cdr (nth (1- (book-current-page)) (book-image-sizes))) + (* 2 (or book-page-vertical-margin pdf-view-image-relief))))) + +(defun pdf-cscroll-first-page () + (interactive) + (pdf-view-goto-page 1)) + +(defun pdf-cscroll-last-page () + (interactive) + (pdf-view-goto-page (pdf-cache-number-of-pages))) + +(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 (not pdf-view-use-scaling) + (car size) + (* 2 (car size))))) + (hotspots (pdf-view-apply-hotspot-functions + window page size))) + (pdf-view-create-image data + :width (car size) + :margin (cons 0 book-page-vertical-margin) + :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-display-page (page &optional window) + "Display page PAGE in WINDOW." + (with-selected-window window + + (let* ((image-sizes (let (s) + (dotimes (i (pdf-info-number-of-pages) (nreverse s)) + (push (pdf-view-desired-image-size (1+ i)) s)))) + (image-positions (book-create-image-positions image-sizes))) + (image-mode-window-put 'image-sizes image-sizes) + (image-mode-window-put 'image-positions image-positions)) + + (let ((inhibit-read-only t)) + (book-create-placeholders))) + + (setf (pdf-view-window-needs-redisplay window) nil) + ;; (setf (pdf-view-current-page window) page) + + (pdf-view-display-triplet page window)) + +(defun pdf-view-display-triplet (page &optional window inhibit-slice-p) + ;; TODO: write documentation! + (let ((ol (pdf-view-current-overlay window)) + (display-pages (book-page-triplet page))) + (when (window-live-p (overlay-get ol 'window)) + (dolist (p (book-currently-displayed-pages window)) + (unless (member p display-pages) + (book-remove-page-image p))) + (dolist (p display-pages) + (let* ((image (pdf-view-create-page p window)) + (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)))))) + (when (= p page) + (setf (pdf-view-current-image window) image)) + ;; In case the window is wider than the image, center the image + ;; horizontally. + (overlay-put (nth (1- p) (book-overlays window)) 'before-string + (when (> (window-width window) + displayed-width) + (propertize " " 'display + `(space :align-to + ,(/ (- (window-width window) + displayed-width) 2))))) + ;; (message "%s %s" p window) + ;; (print (image-property image :type)) + (overlay-put (nth (1- p) (book-overlays window)) 'display + (if slice + (list (cons 'slice + (pdf-util-scale slice size 'round)) + image) + image)) + (cl-pushnew p (book-currently-displayed-pages window)))) + (image-property (overlay-get (nth (1- page) (book-overlays window)) 'display) :type) + (let* ((win (overlay-get ol 'window)) + (hscroll (image-mode-window-get 'hscroll win)) + (vscroll (if-let (vs (image-mode-window-get 'relative-vscroll (image-mode-winprops))) + (round (* vs + (car (last (book-image-positions window))))) + (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))) + ;; (setq test-overlay (overlay-properties (nth (1- page) (book-overlays (print window))))) + (setq currently-displayed-pages display-pages)))) + +(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))))) + (p (pdf-view-current-page))) + (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 (nth (1- p) (book-overlays)) 'before-string + (when (> (window-width window) + displayed-width) + (propertize " " 'display + `(space :align-to + ,(/ (- (window-width window) + displayed-width) 2))))) + (overlay-put (nth (1- p) (book-overlays)) '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) + (print "hello") + (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-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) book-contents-end-pos)) + (setq ol (make-overlay (point-min) book-contents-end-pos 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 (windowp (car winprops)) + (overlay-put ol 'invisible t) + (let* ((image-sizes (let (s) + (dotimes (i (pdf-info-number-of-pages) (nreverse s)) + (push (pdf-view-desired-image-size (1+ i)) s)))) + (image-positions (book-create-image-positions image-sizes))) + (image-mode-window-put 'image-sizes image-sizes winprops) + (image-mode-window-put 'image-positions image-positions winprops)) + (let ((inhibit-read-only t)) + (book-create-overlays-list winprops) + (book-create-placeholders)) + ;; We're not displaying an image yet, so let's do so. This + ;; happens when the buffer is displayed for the first time. + ;; (when (null (print (image-mode-window-get 'image winprops))) + (with-selected-window (car winprops) + (pdf-view-goto-page + (or (image-mode-window-get 'page t) 1)))))) + +(defun pdf-view-mouse-set-region (event &optional allow-extend-p + rectangle-p) + "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. + +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)) + pdf-view-continuous + region) + (when (pdf-util-track-mouse-dragging (event 0.05) + (message "1 %s" (window-vscroll nil t)) + (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)) + (message "2 %s" (window-vscroll nil t)) + (pdf-view-display-region + (cons region pdf-view-active-region) + rectangle-p) + ;; the following somehow messes up activating regions + ;; (pdf-util-scroll-to-edges iregion) + )))) + (setq pdf-view-active-region + (append pdf-view-active-region + (list region))) + (pdf-view--push-mark)))) + + +;;; Fix jump to link (from outline) + +(defun pdf-links-action-perform (link) + "Follow LINK, depending on its type. + +This may turn to another page, switch to another PDF buffer or +invoke `pdf-links-browse-uri-function'. + +Interactively, link is read via `pdf-links-read-link-action'. +This function displays characters around the links in the current +page and starts reading characters (ignoring case). After a +sufficient number of characters have been read, the corresponding +link's link is invoked. Additionally, SPC may be used to +scroll the current page." + (interactive + (list (or (pdf-links-read-link-action "Activate link (SPC scrolls): ") + (error "No link selected")))) + (let-alist link + (cl-case .type + ((goto-dest goto-remote) + (let ((window (selected-window))) + (cl-case .type + (goto-dest + (unless (> .page 0) + (error "Link points to nowhere"))) + (goto-remote + (unless (and .filename (file-exists-p .filename)) + (error "Link points to nonexistent file %s" .filename)) + (setq window (display-buffer + (or (find-buffer-visiting .filename) + (find-file-noselect .filename)))))) + (with-selected-window window + (when (derived-mode-p 'pdf-view-mode) + (when (> .page 0) + (pdf-view-goto-page .page)) + (when .top + ;; Showing the tooltip delays displaying the page for + ;; some reason (sit-for/redisplay don't help), do it + ;; later. + + ;; TODO fix lambda/pdf-util-tooltip-arrow for compatibility with + ;; continuous scroll + ;; (run-with-idle-timer 0.001 nil + ;; (lambda () + ;; (when (window-live-p window) + ;; (with-selected-window window + ;; (when (derived-mode-p 'pdf-view-mode) + ;; (pdf-util-tooltip-arrow .top)))))) + ) + )))) + (uri + (funcall pdf-links-browse-uri-function .uri)) + (t + (error "Unrecognized link type: %s" .type))) + nil)) + +;; (defun pdf-isearch-search-function (string &rest _) +;; "Search for STRING in the current PDF buffer. + +;; This is a Isearch interface function." +;; (when (> (length string) 0) +;; (let ((same-search-p (pdf-isearch-same-search-p)) +;; (oldpage pdf-isearch-current-page) +;; (matches (pdf-isearch-search-page string)) +;; next-match) +;; ;; matches is a list of list of edges ((x0 y1 x1 y2) ...), +;; ;; sorted top to bottom ,left to right. Coordinates are in image +;; ;; space. +;; (unless isearch-forward +;; (setq matches (reverse matches))) +;; (when pdf-isearch-filter-matches-function +;; (setq matches (funcall pdf-isearch-filter-matches-function matches))) +;; ;; Where to go next ? +;; (setq pdf-isearch-current-page (pdf-view-current-page) +;; pdf-isearch-current-matches matches +;; next-match +;; (pdf-isearch-next-match +;; oldpage pdf-isearch-current-page +;; pdf-isearch-current-match matches +;; same-search-p +;; isearch-forward) +;; pdf-isearch-current-parameter +;; (list string isearch-regexp +;; isearch-case-fold-search isearch-word)) +;; (cond +;; (next-match +;; (setq pdf-isearch-current-match next-match) +;; (pdf-isearch-hl-matches next-match matches) +;; (pdf-isearch-focus-match next-match) +;; ;; Don't get off track. +;; (when (or (and (bobp) (not isearch-forward)) +;; (and (eobp) isearch-forward)) +;; (goto-char (1+ (/ (buffer-size) 2)))) +;; ;; Signal success to isearch. +;; (if isearch-forward +;; (re-search-forward ".") +;; (re-search-backward "."))) +;; ((and (not pdf-isearch-narrow-to-page) +;; (not (pdf-isearch-empty-match-p matches))) +;; (let ((next-page (pdf-isearch-find-next-matching-page +;; string pdf-isearch-current-page t))) +;; (when next-page +;; (pdf-view-goto-page next-page) +;; (pdf-isearch-search-function string)))))))) + +;;; Fix occur (TODO fix isearch and remove this function) + +(defun pdf-occur-goto-occurrence (&optional no-select-window-p) + "Go to the occurrence at point. + +If EVENT is nil, use occurrence at current line. Select the +PDF's window, unless NO-SELECT-WINDOW-P is non-nil. + +FIXME: EVENT not used at the moment." + (interactive) + (let ((item (tabulated-list-get-id))) + (when item + (let* ((doc (plist-get item :document)) + (page (plist-get item :page)) + (match (plist-get item :match-edges)) + (buffer (if (bufferp doc) + doc + (or (find-buffer-visiting doc) + (find-file-noselect doc)))) + window) + (if no-select-window-p + (setq window (display-buffer buffer)) + (pop-to-buffer buffer) + (setq window (selected-window))) + (with-selected-window window + (when page + (pdf-view-goto-page page)) + ;; Abuse isearch. + ;; (when match + ;; (let ((pixel-match + ;; (pdf-util-scale-relative-to-pixel match)) + ;; (pdf-isearch-batch-mode t)) + ;; (pdf-isearch-hl-matches pixel-match nil t) + ;; (pdf-isearch-focus-match-batch pixel-match))) + ))))) + + +(define-key pdf-view-mode-map (kbd "C-n") #'pdf-continuous-scroll-forward) +(define-key pdf-view-mode-map (kbd "<down>") #'pdf-continuous-scroll-forward) +(define-key pdf-view-mode-map (kbd "<wheel-down>") #'pdf-cs-mouse-scroll-forward) +(define-key pdf-view-mode-map (kbd "<mouse-5>") #'pdf-cs-mouse-scroll-forward) +(define-key pdf-view-mode-map (kbd "C-p") #'pdf-continuous-scroll-backward) +(define-key pdf-view-mode-map (kbd "<up>") #'pdf-continuous-scroll-backward) +(define-key pdf-view-mode-map (kbd "<wheel-up>") #'pdf-cs-mouse-scroll-backward) +(define-key pdf-view-mode-map (kbd "<mouse-4>") #'pdf-cs-mouse-scroll-backward) +(define-key pdf-view-mode-map "n" #'pdf-continuous-next-page) +(define-key pdf-view-mode-map "p" #'pdf-continuous-previous-page) +(define-key pdf-view-mode-map (kbd "<prior>") 'pdf-continuous-previous-page) +(define-key pdf-view-mode-map (kbd "<next>") 'pdf-continuous-next-page) +;; (define-key pdf-view-mode-map (kbd "M-<") #'pdf-cscroll-view-goto-page) +(define-key pdf-view-mode-map (kbd "M-g g") #'pdf-cscroll-view-goto-page) +(define-key pdf-view-mode-map (kbd "M-g M-g") #'pdf-cscroll-view-goto-page) +(define-key pdf-view-mode-map (kbd "M-<") #'pdf-cscroll-first-page) +(define-key pdf-view-mode-map (kbd "M->") #'pdf-cscroll-last-page) +(define-key pdf-view-mode-map [remap forward-char] #'pdf-cscroll-image-forward-hscroll) +(define-key pdf-view-mode-map [remap right-char] #'pdf-cscroll-image-forward-hscroll) +(define-key pdf-view-mode-map [remap backward-char] #'pdf-cscroll-image-backward-hscroll) +(define-key pdf-view-mode-map [remap left-char] #'pdf-cscroll-image-backward-hscroll) +(define-key pdf-view-mode-map "T" #'pdf-cscroll-toggle-mode-line) +(define-key pdf-view-mode-map "M" #'pdf-cscroll-toggle-narrow-mode-line) +(define-key pdf-view-mode-map (kbd "q") '(lambda () (interactive) (pdf-continuous-scroll-mode -1))) +(define-key pdf-view-mode-map "Q" #'pdf-cscroll-kill-buffer-and-windows) +(define-key pdf-view-mode-map (kbd "C-c C-a l") #'pdf-cscroll-annot-list-annotations) + +(when (boundp 'spacemacs-version) + (evil-define-key 'evilified pdf-view-mode-map + "j" #'pdf-continuous-scroll-forward + (kbd "<mouse-5>") #'pdf-continuous-scroll-forward + "k" #'pdf-continuous-scroll-backward + (kbd "<mouse-4>") #'pdf-continuous-scroll-backward + "J" #'pdf-continuous-next-page + "K" #'pdf-continuous-previous-page + ;; (kbd "C-j") #'pdf-view-scroll-up-or-next-page + ;; (kbd "C-k") #'pdf-view-scroll-down-or-previous-page + (kbd "g t") #'pdf-view-goto-page + (kbd "g g") #'pdf-cscroll-first-page + "G" #'pdf-cscroll-last-page + ;; "M" #'pdf-cscroll-toggle-mode-line + ;; "q" #'pdf-cscroll-kill-buffer-and-windows + ;; "l" #'pdf-cscroll-image-forward-hscroll + ;; "h" #'pdf-cscroll-image-backward-hscroll + )) + +(provide 'pdf-continuous-scroll-mode) + +;;; pdf-continuous-scroll-mode.el ends here |
