500 lines
17 KiB
EmacsLisp
500 lines
17 KiB
EmacsLisp
|
;;; 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-macs)
|
|||
|
(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 ()
|
|||
|
"Initialize the cache to store document data.
|
|||
|
|
|||
|
Note: The cache is only initialized once. After that it needs to
|
|||
|
be cleared before this function makes any changes to it. This is
|
|||
|
an internal function and not meant to be directly used."
|
|||
|
(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)
|
|||
|
"Clear the data cache when annotations are modified.
|
|||
|
|
|||
|
FN is a closure as described in `pdf-annot-modified-functions'.
|
|||
|
|
|||
|
Note: This is an internal function and not meant to be directly used."
|
|||
|
(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 its 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)
|
|||
|
"Remove KEY from the cache of 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)
|
|||
|
"Remove all PAGES from the cache."
|
|||
|
(when pdf-cache--data
|
|||
|
(dolist (page pages)
|
|||
|
(remhash page pdf-cache--data))))
|
|||
|
|
|||
|
(defun pdf-cache-clear-data ()
|
|||
|
"Remove the entire cache."
|
|||
|
(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 its 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)
|
|||
|
"Make the image that we store in the image cache.
|
|||
|
|
|||
|
An image is a tuple of PAGE WIDTH DATA HASH."
|
|||
|
`(list ,page ,width ,data ,hash))
|
|||
|
(defmacro pdf-cache--image/page (img)
|
|||
|
"Return the page value for IMG."
|
|||
|
`(nth 0 ,img))
|
|||
|
(defmacro pdf-cache--image/width (img)
|
|||
|
"Return the width value for IMG."
|
|||
|
`(nth 1 ,img))
|
|||
|
(defmacro pdf-cache--image/data (img)
|
|||
|
"Return the data value for IMG."
|
|||
|
`(nth 2 ,img))
|
|||
|
(defmacro pdf-cache--image/hash (img)
|
|||
|
"Return the hash value for 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, its width
|
|||
|
is at least MIN-WIDTH and at most MAX-WIDTH and its 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.
|
|||
|
|
|||
|
Return an image of at least MIN-WIDTH and, if non-nil, maximum
|
|||
|
width MAX-WIDTH and `eql' HASH value.
|
|||
|
|
|||
|
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)
|
|||
|
"Clear the images cache when annotations are modified.
|
|||
|
|
|||
|
FN is a closure as described in `pdf-annot-modified-functions'.
|
|||
|
|
|||
|
Note: This is an internal function and not meant to be directly used."
|
|||
|
(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)
|
|||
|
"Remove all images of PAGES from the image cache."
|
|||
|
(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 its 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."
|
|||
|
:group 'pdf-cache
|
|||
|
(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 its
|
|||
|
;; 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 ()
|
|||
|
"The default function to prefetch pages.
|
|||
|
|
|||
|
See `pdf-cache-prefetch-pages-function' for an explanation of
|
|||
|
what this function does."
|
|||
|
(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)))))))))
|
|||
|
|
|||
|
(defvar pdf-view-use-scaling)
|
|||
|
(defun pdf-cache--prefetch-pages (window image-width)
|
|||
|
"Internal function to prefetch pages and store them in the cache.
|
|||
|
|
|||
|
WINDOW and IMAGE-WIDTH decide the page and scale of the final image."
|
|||
|
(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)
|
|||
|
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
|