summaryrefslogtreecommitdiff
path: root/elpa/pdf-tools-20200512.1524/pdf-cache.el
diff options
context:
space:
mode:
Diffstat (limited to 'elpa/pdf-tools-20200512.1524/pdf-cache.el')
-rw-r--r--elpa/pdf-tools-20200512.1524/pdf-cache.el456
1 files changed, 456 insertions, 0 deletions
diff --git a/elpa/pdf-tools-20200512.1524/pdf-cache.el b/elpa/pdf-tools-20200512.1524/pdf-cache.el
new file mode 100644
index 0000000..9ed7241
--- /dev/null
+++ b/elpa/pdf-tools-20200512.1524/pdf-cache.el
@@ -0,0 +1,456 @@
+;;; pdf-cache.el --- Cache time-critical or frequent epdfinfo queries. -*- 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:
+;;
+;;; Code:
+;;
+
+(require 'pdf-info)
+(require 'pdf-util)
+
+
+;; * ================================================================== *
+;; * Customiazations
+;; * ================================================================== *
+
+(defcustom pdf-cache-image-limit 64
+ "Maximum number of cached PNG images per buffer."
+ :type 'integer
+ :group 'pdf-cache
+ :group 'pdf-view)
+
+(defcustom pdf-cache-prefetch-delay 0.5
+ "Idle time in seconds before prefetching images starts."
+ :group 'pdf-view
+ :type 'number)
+
+(defcustom pdf-cache-prefetch-pages-function
+ 'pdf-cache-prefetch-pages-function-default
+ "A function returning a list of pages to be prefetched.
+
+It is called with no arguments in the PDF window and should
+return a list of page-numbers, determining the pages that should
+be prefetched and their order."
+ :group 'pdf-view
+ :type 'function)
+
+
+;; * ================================================================== *
+;; * Simple Value cache
+;; * ================================================================== *
+
+(defvar-local pdf-cache--data nil)
+
+(defvar pdf-annot-modified-functions)
+
+(defun pdf-cache--initialize ()
+ (unless pdf-cache--data
+ (setq pdf-cache--data (make-hash-table))
+ (add-hook 'pdf-info-close-document-hook 'pdf-cache-clear-data nil t)
+ (add-hook 'pdf-annot-modified-functions
+ 'pdf-cache--clear-data-of-annotations
+ nil t)))
+
+(defun pdf-cache--clear-data-of-annotations (fn)
+ (apply 'pdf-cache-clear-data-of-pages
+ (mapcar (lambda (a)
+ (cdr (assq 'page a)))
+ (funcall fn t))))
+
+(defun pdf-cache--data-put (key value &optional page)
+ "Put KEY with VALUE in the cache of PAGE, return value."
+ (pdf-cache--initialize)
+ (puthash page (cons (cons key value)
+ (assq-delete-all
+ key
+ (gethash page pdf-cache--data)))
+ pdf-cache--data)
+ value)
+
+(defun pdf-cache--data-get (key &optional page)
+ "Get value of KEY in the cache of PAGE.
+
+Returns a cons \(HIT . VALUE\), where HIT is non-nil if KEY was
+stored previously for PAGE and VALUE it's value. Otherwise HIT
+is nil and VALUE undefined."
+ (pdf-cache--initialize)
+ (let ((elt (assq key (gethash page pdf-cache--data))))
+ (if elt
+ (cons t (cdr elt))
+ (cons nil nil))))
+
+(defun pdf-cache--data-clear (key &optional page)
+ (pdf-cache--initialize)
+ (puthash page
+ (assq-delete-all key (gethash page pdf-cache--data))
+ pdf-cache--data)
+ nil)
+
+(defun pdf-cache-clear-data-of-pages (&rest pages)
+ (when pdf-cache--data
+ (dolist (page pages)
+ (remhash page pdf-cache--data))))
+
+(defun pdf-cache-clear-data ()
+ (interactive)
+ (when pdf-cache--data
+ (clrhash pdf-cache--data)))
+
+(defmacro define-pdf-cache-function (command &optional page-arg-p)
+ "Define a simple data cache function.
+
+COMMAND is the name of the command, e.g. number-of-pages. It
+should have a corresponding pdf-info function. If PAGE-ARG-P is
+non-nil, define a one-dimensional cache indexed by the page
+number. Otherwise the value is constant for each document, like
+e.g. number-of-pages.
+
+Both args are unevaluated."
+
+ (let ((args (if page-arg-p (list 'page)))
+ (fn (intern (format "pdf-cache-%s" command)))
+ (ifn (intern (format "pdf-info-%s" command)))
+ (doc (format "Cached version of `pdf-info-%s', which see.
+
+Make sure, not to modify it's return value." command)))
+ `(defun ,fn ,args
+ ,doc
+ (let ((hit-value (pdf-cache--data-get ',command ,(if page-arg-p 'page))))
+ (if (car hit-value)
+ (cdr hit-value)
+ (pdf-cache--data-put
+ ',command
+ ,(if page-arg-p
+ (list ifn 'page)
+ (list ifn))
+ ,(if page-arg-p 'page)))))))
+
+(define-pdf-cache-function pagelinks t)
+(define-pdf-cache-function number-of-pages)
+;; The boundingbox may change if annotations change.
+(define-pdf-cache-function boundingbox t)
+(define-pdf-cache-function textregions t)
+(define-pdf-cache-function pagesize t)
+
+
+;; * ================================================================== *
+;; * PNG image LRU cache
+;; * ================================================================== *
+
+(defvar pdf-cache-image-inihibit nil
+ "Non-nil, if the image cache should be bypassed.")
+
+(defvar-local pdf-cache--image-cache nil)
+
+(defmacro pdf-cache--make-image (page width data hash)
+ `(list ,page ,width ,data ,hash))
+(defmacro pdf-cache--image/page (img) `(nth 0 ,img))
+(defmacro pdf-cache--image/width (img) `(nth 1 ,img))
+(defmacro pdf-cache--image/data (img) `(nth 2 ,img))
+(defmacro pdf-cache--image/hash (img) `(nth 3 ,img))
+
+(defun pdf-cache--image-match (image page min-width &optional max-width hash)
+ "Match IMAGE with specs.
+
+IMAGE should be a list as created by `pdf-cache--make-image'.
+
+Return non-nil, if IMAGE's page is the same as PAGE, it's width
+is at least MIN-WIDTH and at most MAX-WIDTH and it's stored
+hash-value is `eql' to HASH."
+ (and (= (pdf-cache--image/page image)
+ page)
+ (or (null min-width)
+ (>= (pdf-cache--image/width image)
+ min-width))
+ (or (null max-width)
+ (<= (pdf-cache--image/width image)
+ max-width))
+ (eql (pdf-cache--image/hash image)
+ hash)))
+
+(defun pdf-cache-lookup-image (page min-width &optional max-width hash)
+ "Return PAGE's cached PNG data as a string or nil.
+
+Does not modify the cache. See also `pdf-cache-get-image'."
+ (let ((image (car (cl-member
+ (list page min-width max-width hash)
+ pdf-cache--image-cache
+ :test (lambda (spec image)
+ (apply 'pdf-cache--image-match image spec))))))
+ (and image
+ (pdf-cache--image/data image))))
+
+(defun pdf-cache-get-image (page min-width &optional max-width hash)
+ "Return PAGE's PNG data as a string.
+
+Return an image of at least MIN-WIDTH and, if non-nil, maximum
+width MAX-WIDTH and `eql' hash value.
+
+Remember that image was recently used.
+
+Returns nil, if no matching image was found."
+ (let ((cache pdf-cache--image-cache)
+ image)
+ ;; Find it in the cache.
+ (while (and (setq image (pop cache))
+ (not (pdf-cache--image-match
+ image page min-width max-width hash))))
+ ;; Remove it and push it to the front.
+ (when image
+ (setq pdf-cache--image-cache
+ (cons image (delq image pdf-cache--image-cache)))
+ (pdf-cache--image/data image))))
+
+(defun pdf-cache-put-image (page width data &optional hash)
+ "Cache image of PAGE with WIDTH, DATA and HASH.
+
+DATA should the string of a PNG image of width WIDTH and from
+page PAGE in the current buffer. See `pdf-cache-get-image' for
+the HASH argument.
+
+This function always returns nil."
+ (unless pdf-cache--image-cache
+ (add-hook 'pdf-info-close-document-hook 'pdf-cache-clear-images nil t)
+ (add-hook 'pdf-annot-modified-functions
+ 'pdf-cache--clear-images-of-annotations nil t))
+ (push (pdf-cache--make-image page width data hash)
+ pdf-cache--image-cache)
+ ;; Forget old image(s).
+ (when (> (length pdf-cache--image-cache)
+ pdf-cache-image-limit)
+ (if (> pdf-cache-image-limit 1)
+ (setcdr (nthcdr (1- pdf-cache-image-limit)
+ pdf-cache--image-cache)
+ nil)
+ (setq pdf-cache--image-cache nil)))
+ nil)
+
+(defun pdf-cache-clear-images ()
+ "Clear the image cache."
+ (setq pdf-cache--image-cache nil))
+
+(defun pdf-cache-clear-images-if (fn)
+ "Remove images from the cache according to FN.
+
+FN should be function accepting 4 Arguments \(PAGE WIDTH DATA
+HASH\). It should return non-nil, if the image should be removed
+from the cache."
+ (setq pdf-cache--image-cache
+ (cl-remove-if
+ (lambda (image)
+ (funcall
+ fn
+ (pdf-cache--image/page image)
+ (pdf-cache--image/width image)
+ (pdf-cache--image/data image)
+ (pdf-cache--image/hash image)))
+ pdf-cache--image-cache)))
+
+
+(defun pdf-cache--clear-images-of-annotations (fn)
+ (apply 'pdf-cache-clear-images-of-pages
+ (mapcar (lambda (a)
+ (cdr (assq 'page a)))
+ (funcall fn t))))
+
+(defun pdf-cache-clear-images-of-pages (&rest pages)
+ (pdf-cache-clear-images-if
+ (lambda (page &rest _) (memq page pages))))
+
+(defun pdf-cache-renderpage (page min-width &optional max-width)
+ "Render PAGE according to MIN-WIDTH and MAX-WIDTH.
+
+Return the PNG data of an image as a string, such that it's width
+is at least MIN-WIDTH and, if non-nil, at most MAX-WIDTH.
+
+If such an image is not available in the cache, call
+`pdf-info-renderpage' to create one."
+ (if pdf-cache-image-inihibit
+ (pdf-info-renderpage page min-width)
+ (or (pdf-cache-get-image page min-width max-width)
+ (let ((data (pdf-info-renderpage page min-width)))
+ (pdf-cache-put-image page min-width data)
+ data))))
+
+(defun pdf-cache-renderpage-text-regions (page width single-line-p
+ &rest selection)
+ "Render PAGE according to WIDTH, SINGLE-LINE-P and SELECTION.
+
+See also `pdf-info-renderpage-text-regions' and
+`pdf-cache-renderpage'."
+ (if pdf-cache-image-inihibit
+ (apply 'pdf-info-renderpage-text-regions
+ page width single-line-p nil selection)
+ (let ((hash (sxhash
+ (format "%S" (cons 'renderpage-text-regions
+ (cons single-line-p selection))))))
+ (or (pdf-cache-get-image page width width hash)
+ (let ((data (apply 'pdf-info-renderpage-text-regions
+ page width single-line-p nil selection)))
+ (pdf-cache-put-image page width data hash)
+ data)))))
+
+(defun pdf-cache-renderpage-highlight (page width &rest regions)
+ "Highlight PAGE according to WIDTH and REGIONS.
+
+See also `pdf-info-renderpage-highlight' and
+`pdf-cache-renderpage'."
+ (if pdf-cache-image-inihibit
+ (apply 'pdf-info-renderpage-highlight
+ page width nil regions)
+ (let ((hash (sxhash
+ (format "%S" (cons 'renderpage-highlight
+ regions)))))
+ (or (pdf-cache-get-image page width width hash)
+ (let ((data (apply 'pdf-info-renderpage-highlight
+ page width nil regions)))
+ (pdf-cache-put-image page width data hash)
+ data)))))
+
+
+;; * ================================================================== *
+;; * Prefetching images
+;; * ================================================================== *
+
+(defvar-local pdf-cache--prefetch-pages nil
+ "Pages to be prefetched.")
+
+(defvar-local pdf-cache--prefetch-timer nil
+ "Timer used when prefetching images.")
+
+(define-minor-mode pdf-cache-prefetch-minor-mode
+ "Try to load images which will probably be needed in a while."
+ nil nil nil
+ (pdf-cache--prefetch-cancel)
+ (cond
+ (pdf-cache-prefetch-minor-mode
+ (pdf-util-assert-pdf-buffer)
+ (add-hook 'pre-command-hook 'pdf-cache--prefetch-stop nil t)
+ ;; FIXME: Disable the time when the buffer is killed or it's
+ ;; major-mode changes.
+ (setq pdf-cache--prefetch-timer
+ (run-with-idle-timer (or pdf-cache-prefetch-delay 1)
+ t 'pdf-cache--prefetch-start (current-buffer))))
+ (t
+ (remove-hook 'pre-command-hook 'pdf-cache--prefetch-stop t))))
+
+(defun pdf-cache-prefetch-pages-function-default ()
+ (let ((page (pdf-view-current-page)))
+ (pdf-util-remove-duplicates
+ (cl-remove-if-not
+ (lambda (page)
+ (and (>= page 1)
+ (<= page (pdf-cache-number-of-pages))))
+ (append
+ ;; +1, -1, +2, -2, ...
+ (let ((sign 1)
+ (incr 1))
+ (mapcar (lambda (_)
+ (setq page (+ page (* sign incr))
+ sign (- sign)
+ incr (1+ incr))
+ page)
+ (number-sequence 1 16)))
+ ;; First and last
+ (list 1 (pdf-cache-number-of-pages))
+ ;; Links
+ (mapcar
+ (apply-partially 'alist-get 'page)
+ (cl-remove-if-not
+ (lambda (link) (eq (alist-get 'type link) 'goto-dest))
+ (pdf-cache-pagelinks
+ (pdf-view-current-page)))))))))
+
+(defun pdf-cache--prefetch-pages (window image-width)
+ (when (and (eq window (selected-window))
+ (pdf-util-pdf-buffer-p))
+ (let ((page (pop pdf-cache--prefetch-pages)))
+ (while (and page
+ (pdf-cache-lookup-image
+ page
+ image-width
+ (if (not (pdf-view-use-scaling-p))
+ image-width
+ (* 2 image-width))))
+ (setq page (pop pdf-cache--prefetch-pages)))
+ (pdf-util-debug
+ (when (null page)
+ (message "Prefetching done.")))
+ (when page
+ (let* ((buffer (current-buffer))
+ (pdf-info-asynchronous
+ (lambda (status data)
+ (when (and (null status)
+ (eq window
+ (selected-window))
+ (eq buffer (window-buffer)))
+ (with-current-buffer (window-buffer)
+ (when (derived-mode-p 'pdf-view-mode)
+ (pdf-cache-put-image
+ page image-width data)
+ (image-size (pdf-view-create-page page))
+ (pdf-util-debug
+ (message "Prefetched page %s." page))
+ ;; Avoid max-lisp-eval-depth
+ (run-with-timer
+ 0.001 nil 'pdf-cache--prefetch-pages window image-width)))))))
+ (condition-case err
+ (pdf-info-renderpage page image-width)
+ (error
+ (pdf-cache-prefetch-minor-mode -1)
+ (signal (car err) (cdr err)))))))))
+
+(defvar pdf-cache--prefetch-started-p nil
+ "Guard against multiple prefetch starts.
+
+Used solely in `pdf-cache--prefetch-start'.")
+
+(defun pdf-cache--prefetch-start (buffer)
+ "Start prefetching images in BUFFER."
+ (when (and pdf-cache-prefetch-minor-mode
+ (not pdf-cache--prefetch-started-p)
+ (pdf-util-pdf-buffer-p)
+ (not isearch-mode)
+ (null pdf-cache--prefetch-pages)
+ (eq (window-buffer) buffer)
+ (fboundp pdf-cache-prefetch-pages-function))
+ (let* ((pdf-cache--prefetch-started-p t)
+ (pages (funcall pdf-cache-prefetch-pages-function)))
+ (setq pdf-cache--prefetch-pages
+ (butlast pages (max 0 (- (length pages)
+ pdf-cache-image-limit))))
+ (pdf-cache--prefetch-pages
+ (selected-window)
+ (car (pdf-view-desired-image-size))))))
+
+(defun pdf-cache--prefetch-stop ()
+ "Stop prefetching images in current buffer."
+ (setq pdf-cache--prefetch-pages nil))
+
+(defun pdf-cache--prefetch-cancel ()
+ "Cancel prefetching images in current buffer."
+ (pdf-cache--prefetch-stop)
+ (when pdf-cache--prefetch-timer
+ (cancel-timer pdf-cache--prefetch-timer))
+ (setq pdf-cache--prefetch-timer nil))
+
+(provide 'pdf-cache)
+;;; pdf-cache.el ends here
Copyright 2019--2024 Marius PETER