summaryrefslogtreecommitdiff
path: root/elpa/pdf-tools-20200512.1524/pdf-util.el
diff options
context:
space:
mode:
Diffstat (limited to 'elpa/pdf-tools-20200512.1524/pdf-util.el')
-rw-r--r--elpa/pdf-tools-20200512.1524/pdf-util.el1346
1 files changed, 1346 insertions, 0 deletions
diff --git a/elpa/pdf-tools-20200512.1524/pdf-util.el b/elpa/pdf-tools-20200512.1524/pdf-util.el
new file mode 100644
index 0000000..cdb0f26
--- /dev/null
+++ b/elpa/pdf-tools-20200512.1524/pdf-util.el
@@ -0,0 +1,1346 @@
+;;; pdf-util.el --- PDF Utility functions. -*- lexical-binding: t -*-
+
+;; Copyright (C) 2013, 2014 Andreas Politz
+
+;; Author: Andreas Politz <politza@fh-trier.de>
+;; Keywords: files, multimedia
+
+;; 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:
+;;
+;;; Todo:
+;;
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'format-spec)
+(require 'faces)
+
+;; These functions are only used after a PdfView window was asserted,
+;; which won't succeed, if pdf-view.el isn't loaded.
+(declare-function pdf-view-image-size "pdf-view")
+(declare-function pdf-view-image-offset "pdf-view")
+(declare-function pdf-view-current-image "pdf-view")
+(declare-function pdf-view-current-overlay "pdf-view")
+(declare-function pdf-cache-pagesize "pdf-cache")
+
+(declare-function pdf-view-image-type "pdf-view")
+
+
+
+;; * ================================================================== *
+;; * Compatibility with older Emacssen (< 25.1)
+;; * ================================================================== *
+
+;; The with-file-modes macro is only available in recent Emacs
+;; versions.
+(eval-when-compile
+ (unless (fboundp 'with-file-modes)
+ (defmacro with-file-modes (modes &rest body)
+ "Execute BODY with default file permissions temporarily set to MODES.
+MODES is as for `set-default-file-modes'."
+ (declare (indent 1) (debug t))
+ (let ((umask (make-symbol "umask")))
+ `(let ((,umask (default-file-modes)))
+ (unwind-protect
+ (progn
+ (set-default-file-modes ,modes)
+ ,@body)
+ (set-default-file-modes ,umask)))))))
+
+(unless (fboundp 'alist-get) ;;25.1
+ (defun alist-get (key alist &optional default remove)
+ "Get the value associated to KEY in ALIST.
+DEFAULT is the value to return if KEY is not found in ALIST.
+REMOVE, if non-nil, means that when setting this element, we should
+remove the entry if the new value is `eql' to DEFAULT."
+ (ignore remove) ;;Silence byte-compiler.
+ (let ((x (assq key alist)))
+ (if x (cdr x) default))))
+
+(require 'register)
+(unless (fboundp 'register-read-with-preview)
+ (defalias 'register-read-with-preview 'read-char
+ "Compatibility alias for pdf-tools."))
+
+;; In Emacs 24.3 window-width does not have a PIXELWISE argument.
+(defmacro pdf-util-window-pixel-width (&optional window)
+ "Return the width of WINDOW in pixel."
+ (if (< (cdr (subr-arity (symbol-function 'window-body-width))) 2)
+ (let ((window* (make-symbol "window")))
+ `(let ((,window* ,window))
+ (* (window-body-width ,window*)
+ (frame-char-width (window-frame ,window*)))))
+ `(window-body-width ,window t)))
+
+;; In Emacs 24.3 image-mode-winprops leads to infinite recursion.
+(unless (or (> emacs-major-version 24)
+ (and (= emacs-major-version 24)
+ (>= emacs-minor-version 4)))
+ (require 'image-mode)
+ (defvar image-mode-winprops-original-function
+ (symbol-function 'image-mode-winprops))
+ (eval-after-load "image-mode"
+ '(defun image-mode-winprops (&optional window cleanup)
+ (if (not (eq major-mode 'pdf-view-mode))
+ (funcall image-mode-winprops-original-function
+ window cleanup)
+ (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 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)))))
+
+
+
+;; * ================================================================== *
+;; * Transforming coordinates
+;; * ================================================================== *
+
+
+(defun pdf-util-scale (list-of-edges-or-pos scale &optional rounding-fn)
+ "Scale LIST-OF-EDGES-OR-POS by SCALE.
+
+SCALE is a cons (SX . SY), by which edges/positions are scaled.
+If ROUNDING-FN is non-nil, it should be a function of one
+argument, a real value, returning a rounded
+value (e.g. `ceiling').
+
+The elements in LIST-OF-EDGES-OR-POS should be either a list
+\(LEFT TOP RIGHT BOT\) or a position \(X . Y\).
+
+LIST-OF-EDGES-OR-POS may also be a single such element.
+
+Return scaled list of edges if LIST-OF-EDGES-OR-POS was indeed a list,
+else return the scaled singleton."
+
+ (let ((have-list-p (listp (car list-of-edges-or-pos))))
+ (unless have-list-p
+ (setq list-of-edges-or-pos (list list-of-edges-or-pos)))
+ (let* ((sx (car scale))
+ (sy (cdr scale))
+ (result
+ (mapcar
+ (lambda (edges)
+ (cond
+ ((consp (cdr edges))
+ (let ((e (list (* (nth 0 edges) sx)
+ (* (nth 1 edges) sy)
+ (* (nth 2 edges) sx)
+ (* (nth 3 edges) sy))))
+ (if rounding-fn
+ (mapcar rounding-fn e)
+ e)))
+ (rounding-fn
+ (cons (funcall rounding-fn (* (car edges) sx))
+ (funcall rounding-fn (* (cdr edges) sy))))
+ (t
+ (cons (* (car edges) sx)
+ (* (cdr edges) sy)))))
+ list-of-edges-or-pos)))
+ (if have-list-p
+ result
+ (car result)))))
+
+(defun pdf-util-scale-to (list-of-edges from to &optional rounding-fn)
+ "Scale LIST-OF-EDGES in FROM basis to TO.
+
+FROM and TO should both be a cons \(WIDTH . HEIGHT\). See also
+`pdf-util-scale'."
+
+ (pdf-util-scale list-of-edges
+ (cons (/ (float (car to))
+ (float (car from)))
+ (/ (float (cdr to))
+ (float (cdr from))))
+ rounding-fn))
+
+(defun pdf-util-scale-pixel-to-points (list-of-pixel-edges
+ &optional rounding-fn displayed-p window)
+ "Scale LIST-OF-PIXEL-EDGES to point values.
+
+The result depends on the currently displayed page in WINDOW.
+See also `pdf-util-scale'."
+ (pdf-util-assert-pdf-window window)
+ (pdf-util-scale-to
+ list-of-pixel-edges
+ (pdf-view-image-size displayed-p window)
+ (pdf-cache-pagesize (pdf-view-current-page window))
+ rounding-fn))
+
+(defun pdf-util-scale-points-to-pixel (list-of-points-edges
+ &optional rounding-fn displayed-p window)
+ "Scale LIST-OF-POINTS-EDGES to point values.
+
+The result depends on the currently displayed page in WINDOW.
+See also `pdf-util-scale'."
+ (pdf-util-assert-pdf-window window)
+ (pdf-util-scale-to
+ list-of-points-edges
+ (pdf-cache-pagesize (pdf-view-current-page window))
+ (pdf-view-image-size displayed-p window)
+ rounding-fn))
+
+(defun pdf-util-scale-relative-to-points (list-of-relative-edges
+ &optional rounding-fn window)
+ "Scale LIST-OF-RELATIVE-EDGES to point values.
+
+The result depends on the currently displayed page in WINDOW.
+See also `pdf-util-scale'."
+ (pdf-util-assert-pdf-window window)
+ (pdf-util-scale-to
+ list-of-relative-edges
+ '(1.0 . 1.0)
+ (pdf-cache-pagesize (pdf-view-current-page window))
+ rounding-fn))
+
+(defun pdf-util-scale-points-to-relative (list-of-points-edges
+ &optional rounding-fn window)
+ "Scale LIST-OF-POINTS-EDGES to relative values.
+
+See also `pdf-util-scale'."
+ (pdf-util-assert-pdf-window window)
+ (pdf-util-scale-to
+ list-of-points-edges
+ (pdf-cache-pagesize (pdf-view-current-page window))
+ '(1.0 . 1.0)
+ rounding-fn))
+
+(defun pdf-util-scale-pixel-to-relative (list-of-pixel-edges
+ &optional rounding-fn displayed-p window)
+ "Scale LIST-OF-PIXEL-EDGES to relative values.
+
+The result depends on the currently displayed page in WINDOW.
+See also `pdf-util-scale'."
+ (pdf-util-assert-pdf-window window)
+ (pdf-util-scale-to
+ list-of-pixel-edges
+ (pdf-view-image-size displayed-p window)
+ '(1.0 . 1.0)
+ rounding-fn))
+
+
+(defun pdf-util-scale-relative-to-pixel (list-of-relative-edges
+ &optional rounding-fn displayed-p window)
+ "Scale LIST-OF-EDGES to match SIZE.
+
+The result depends on the currently displayed page in WINDOW.
+See also `pdf-util-scale'."
+ (pdf-util-assert-pdf-window window)
+ (pdf-util-scale-to
+ list-of-relative-edges
+ '(1.0 . 1.0)
+ (pdf-view-image-size displayed-p window)
+ rounding-fn))
+
+(defun pdf-util-translate (list-of-edges-or-pos
+ offset &optional opposite-direction-p)
+ "Translate LIST-OF-EDGES-OR-POS by OFFSET
+
+OFFSET should be a cons \(X . Y\), by which to translate
+LIST-OF-EDGES-OR-POS. If OPPOSITE-DIRECTION-P is non-nil
+translate by \(-X . -Y\).
+
+See `pdf-util-scale' for the LIST-OF-EDGES-OR-POS argument."
+
+ (let ((have-list-p (listp (car list-of-edges-or-pos))))
+ (unless have-list-p
+ (setq list-of-edges-or-pos (list list-of-edges-or-pos)))
+ (let* ((ox (if opposite-direction-p
+ (- (car offset))
+ (car offset)))
+ (oy (if opposite-direction-p
+ (- (cdr offset))
+ (cdr offset)))
+ (result
+ (mapcar
+ (lambda (edges)
+ (cond
+ ((consp (cdr edges))
+ (list (+ (nth 0 edges) ox)
+ (+ (nth 1 edges) oy)
+ (+ (nth 2 edges) ox)
+ (+ (nth 3 edges) oy)))
+ (t
+ (cons (+ (car edges) ox)
+ (+ (cdr edges) oy)))))
+ list-of-edges-or-pos)))
+ (if have-list-p
+ result
+ (car result)))))
+
+(defun pdf-util-edges-transform (region elts &optional to-region-p)
+ "Translate ELTS according to REGION.
+
+ELTS may be one edges list or a position or a list thereof.
+Translate each from region coordinates to (0 0 1 1) or the
+opposite, if TO-REGION-P is non-nil. All coordinates should be
+relative.
+
+Returns the translated list of elements or the single one
+depending on the input."
+
+ (when elts
+ (let ((have-list-p (consp (car-safe elts))))
+ (unless have-list-p
+ (setq elts (list elts)))
+ (let ((result
+ (if (null region)
+ elts
+ (mapcar (lambda (edges)
+ (let ((have-pos-p (numberp (cdr edges))))
+ (when have-pos-p
+ (setq edges (list (car edges) (cdr edges)
+ (car edges) (cdr edges))))
+ (pdf-util-with-edges (edges region)
+ (let ((newedges
+ (mapcar (lambda (n)
+ (min 1.0 (max 0.0 n)))
+ (if to-region-p
+ `(,(/ (- edges-left region-left)
+ region-width)
+ ,(/ (- edges-top region-top)
+ region-height)
+ ,(/ (- edges-right region-left)
+ region-width)
+ ,(/ (- edges-bot region-top)
+ region-height))
+ `(,(+ (* edges-left region-width)
+ region-left)
+ ,(+ (* edges-top region-height)
+ region-top)
+ ,(+ (* edges-right region-width)
+ region-left)
+ ,(+ (* edges-bot region-height)
+ region-top))))))
+ (if have-pos-p
+ (cons (car newedges) (cadr newedges))
+ newedges)))))
+ elts))))
+ (if have-list-p
+ result
+ (car result))))))
+
+(defmacro pdf-util-with-edges (list-of-edges &rest body)
+ "Provide some convenient macros for the edges in LIST-OF-EDGES.
+
+LIST-OF-EDGES should be a list of variables \(X ...\), each one
+holding a list of edges. Inside BODY the symbols X-left, X-top,
+X-right, X-bot, X-width and X-height expand to their respective
+values."
+
+ (declare (indent 1) (debug (sexp &rest form)))
+ (unless (cl-every 'symbolp list-of-edges)
+ (error "Argument should be a list of symbols"))
+ (let ((list-of-syms
+ (mapcar (lambda (edge)
+ (cons edge (mapcar
+ (lambda (kind)
+ (intern (format "%s-%s" edge kind)))
+ '(left top right bot width height))))
+ list-of-edges)))
+ (macroexpand-all
+ `(cl-symbol-macrolet
+ ,(apply 'nconc
+ (mapcar
+ (lambda (edge-syms)
+ (let ((edge (nth 0 edge-syms))
+ (syms (cdr edge-syms)))
+ `((,(pop syms) (nth 0 ,edge))
+ (,(pop syms) (nth 1 ,edge))
+ (,(pop syms) (nth 2 ,edge))
+ (,(pop syms) (nth 3 ,edge))
+ (,(pop syms) (- (nth 2 ,edge)
+ (nth 0 ,edge)))
+ (,(pop syms) (- (nth 3 ,edge)
+ (nth 1 ,edge))))))
+ list-of-syms))
+ ,@body))))
+
+
+;; * ================================================================== *
+;; * Scrolling
+;; * ================================================================== *
+
+(defun pdf-util-image-displayed-edges (&optional window displayed-p)
+ "Return the visible region of the image in WINDOW.
+
+Returns a list of pixel edges."
+ (pdf-util-assert-pdf-window)
+ (let* ((edges (window-inside-pixel-edges window))
+ (isize (pdf-view-image-size displayed-p window))
+ (offset (if displayed-p
+ `(0 . 0)
+ (pdf-view-image-offset window)))
+ (hscroll (* (window-hscroll window)
+ (frame-char-width (window-frame window))))
+ (vscroll (window-vscroll window t))
+ (x0 (+ hscroll (car offset)))
+ (y0 (+ vscroll (cdr offset)))
+ (x1 (min (car isize)
+ (+ x0 (- (nth 2 edges) (nth 0 edges)))))
+ (y1 (min (cdr isize)
+ (+ y0 (- (nth 3 edges) (nth 1 edges))))))
+ (mapcar 'round (list x0 y0 x1 y1))))
+
+(defun pdf-util-required-hscroll (edges &optional eager-p context-pixel)
+ "Return the amount of scrolling necessary, to make image EDGES visible.
+
+Scroll as little as necessary. Unless EAGER-P is non-nil, in
+which case scroll as much as possible.
+
+Keep CONTEXT-PIXEL pixel of the image visible at the bottom and
+top of the window. CONTEXT-PIXEL defaults to 0.
+
+Return the required hscroll in columns or nil, if scrolling is not
+needed."
+
+ (pdf-util-assert-pdf-window)
+ (unless context-pixel
+ (setq context-pixel 0))
+ (let* ((win (window-inside-pixel-edges))
+ (image-width (car (pdf-view-image-size t)))
+ (image-left (* (frame-char-width)
+ (window-hscroll)))
+ (edges (pdf-util-translate
+ edges
+ (pdf-view-image-offset) t)))
+ (pdf-util-with-edges (win edges)
+ (let* ((edges-left (- edges-left context-pixel))
+ (edges-right (+ edges-right context-pixel)))
+ (if (< edges-left image-left)
+ (round (/ (max 0 (if eager-p
+ (- edges-right win-width)
+ edges-left))
+ (frame-char-width)))
+ (if (> (min image-width
+ edges-right)
+ (+ image-left win-width))
+ (round (/ (min (- image-width win-width)
+ (if eager-p
+ edges-left
+ (- edges-right win-width)))
+ (frame-char-width)))))))))
+
+(defun pdf-util-required-vscroll (edges &optional eager-p context-pixel)
+ "Return the amount of scrolling necessary, to make image EDGES visible.
+
+Scroll as little as necessary. Unless EAGER-P is non-nil, in
+which case scroll as much as possible.
+
+Keep CONTEXT-PIXEL pixel of the image visible at the bottom and
+top of the window. CONTEXT-PIXEL defaults to an equivalent pixel
+value of `next-screen-context-lines'.
+
+Return the required vscroll in lines or nil, if scrolling is not
+needed."
+
+ (pdf-util-assert-pdf-window)
+ (let* ((win (window-inside-pixel-edges))
+ (image-height (cdr (pdf-view-image-size t)))
+ (image-top (window-vscroll nil t))
+ (edges (pdf-util-translate
+ edges
+ (pdf-view-image-offset) t)))
+ (pdf-util-with-edges (win edges)
+ (let* ((context-pixel (or context-pixel
+ (* next-screen-context-lines
+ (frame-char-height))))
+ ;;Be careful not to modify edges.
+ (edges-top (- edges-top context-pixel))
+ (edges-bot (+ edges-bot context-pixel)))
+ (if (< edges-top image-top)
+ (round (/ (max 0 (if eager-p
+ (- edges-bot win-height)
+ edges-top))
+ (float (frame-char-height))))
+ (if (> (min image-height
+ edges-bot)
+ (+ image-top win-height))
+ (round (/ (min (- image-height win-height)
+ (if eager-p
+ edges-top
+ (- edges-bot win-height)))
+ (float (frame-char-height))))))))))
+
+(defun pdf-util-scroll-to-edges (edges &optional eager-p)
+ "Scroll window such that image EDGES are visible.
+
+Scroll as little as necessary. Unless EAGER-P is non-nil, in
+which case scroll as much as possible."
+
+ (let ((vscroll (pdf-util-required-vscroll edges eager-p))
+ (hscroll (pdf-util-required-hscroll edges eager-p)))
+ (when vscroll
+ (image-set-window-vscroll vscroll))
+ (when hscroll
+ (image-set-window-hscroll hscroll))))
+
+
+
+;; * ================================================================== *
+;; * Temporary files
+;; * ================================================================== *
+
+(defvar pdf-util--base-directory nil
+ "Base directory for temporary files.")
+
+(defvar-local pdf-util--dedicated-directory nil
+ "The relative name of buffer's dedicated directory.")
+
+(defun pdf-util-dedicated-directory ()
+ "Return the name of a existing dedicated directory.
+
+The directory is exclusive to the current buffer. It will be
+automatically deleted, if Emacs or the current buffer are
+killed."
+ (with-file-modes #o0700
+ (unless (and pdf-util--base-directory
+ (file-directory-p
+ pdf-util--base-directory)
+ (not (file-symlink-p
+ pdf-util--base-directory)))
+ (add-hook 'kill-emacs-hook
+ (lambda nil
+ (when (and pdf-util--base-directory
+ (file-directory-p pdf-util--base-directory))
+ (delete-directory pdf-util--base-directory t))))
+ (setq pdf-util--base-directory
+ (make-temp-file "pdf-tools-" t)))
+ (unless (and pdf-util--dedicated-directory
+ (file-directory-p pdf-util--dedicated-directory)
+ (not (file-symlink-p
+ pdf-util--base-directory)))
+ (let ((temporary-file-directory
+ pdf-util--base-directory))
+ (setq pdf-util--dedicated-directory
+ (make-temp-file (convert-standard-filename
+ (concat (if buffer-file-name
+ (file-name-nondirectory
+ buffer-file-name)
+ (buffer-name))
+ "-"))
+ t))
+ (add-hook 'kill-buffer-hook 'pdf-util-delete-dedicated-directory
+ nil t)))
+ pdf-util--dedicated-directory))
+
+(defun pdf-util-delete-dedicated-directory ()
+ "Delete current buffer's dedicated directory."
+ (delete-directory (pdf-util-dedicated-directory) t))
+
+(defun pdf-util-expand-file-name (name)
+ "Expand filename against current buffer's dedicated directory."
+ (expand-file-name name (pdf-util-dedicated-directory)))
+
+(defun pdf-util-make-temp-file (prefix &optional dir-flag suffix)
+ "Create a temporary file in current buffer's dedicated directory.
+
+See `make-temp-file' for the arguments."
+ (let ((temporary-file-directory
+ (pdf-util-dedicated-directory)))
+ (make-temp-file (convert-standard-filename prefix) dir-flag suffix)))
+
+
+;; * ================================================================== *
+;; * Various
+;; * ================================================================== *
+
+(defmacro pdf-util-debug (&rest body)
+ "Execute BODY only if debugging is enabled."
+ (declare (indent 0) (debug t))
+ `(when (bound-and-true-p pdf-tools-debug)
+ ,@body))
+
+(defun pdf-util-pdf-buffer-p (&optional buffer)
+ (and (or (null buffer)
+ (buffer-live-p buffer))
+ (save-current-buffer
+ (and buffer (set-buffer buffer))
+ (derived-mode-p 'pdf-view-mode))))
+
+(defun pdf-util-assert-pdf-buffer (&optional buffer)
+ (unless (pdf-util-pdf-buffer-p buffer)
+ (error "Buffer is not in PDFView mode")))
+
+(defun pdf-util-pdf-window-p (&optional window)
+ (unless (or (null window)
+ (window-live-p window))
+ (signal 'wrong-type-argument (list 'window-live-p window)))
+ (unless window (setq window (selected-window)))
+ (and (window-live-p window)
+ (with-selected-window window
+ (pdf-util-pdf-buffer-p))))
+
+(defun pdf-util-assert-pdf-window (&optional window)
+ (unless (pdf-util-pdf-window-p window)
+ (error "Window's buffer is not in PdfView mode")))
+
+(defun pdf-util-munch-file (filename &optional multibyte-p)
+ "Read contents from FILENAME and delete it.
+
+Return the file's content as a unibyte string, unless MULTIBYTE-P
+is non-nil."
+ (unwind-protect
+ (with-temp-buffer
+ (set-buffer-multibyte multibyte-p)
+ (insert-file-contents-literally filename)
+ (buffer-substring-no-properties
+ (point-min)
+ (point-max)))
+ (when (and filename
+ (file-exists-p filename))
+ (delete-file filename))))
+
+(defun pdf-util-hexcolor (color)
+ "Return COLOR in hex-format.
+
+Signal an error, if color is invalid."
+ (if (string-match "\\`#[[:xdigit:]]\\{6\\}\\'" color)
+ color
+ (let ((values (color-values color)))
+ (unless values
+ (signal 'wrong-type-argument (list 'color-defined-p color)))
+ (apply 'format "#%02x%02x%02x"
+ (mapcar (lambda (c) (lsh c -8))
+ values)))))
+
+(defun pdf-util-highlight-regexp-in-string (regexp string &optional face)
+ "Highlight all occurrences of REGEXP in STRING using FACE.
+
+FACE defaults to the `match' face. Returns the new fontified
+string."
+ (with-temp-buffer
+ (save-excursion (insert string))
+ (while (and (not (eobp))
+ (re-search-forward regexp nil t))
+ (if (= (match-beginning 0)
+ (match-end 0))
+ (forward-char)
+ (put-text-property
+ (match-beginning 0)
+ (point)
+ 'face (or face 'match))))
+ (buffer-string)))
+
+(defun pdf-util-color-completions ()
+ "Return a fontified list of defined colors."
+ (let ((color-list (list-colors-duplicates))
+ colors)
+ (dolist (cl color-list)
+ (dolist (c (reverse cl))
+ (push (propertize c 'face `(:background ,c))
+ colors)))
+ (nreverse colors)))
+
+(defun pdf-util-tooltip-in-window (text x y &optional window)
+ (let* ((we (window-inside-absolute-pixel-edges window))
+ (dx (round (+ x (nth 0 we))))
+ (dy (round (+ y (nth 1 we))))
+ (tooltip-frame-parameters
+ `((left . ,dx)
+ (top . ,dy)
+ ,@tooltip-frame-parameters)))
+ (tooltip-show text)))
+
+(defun pdf-util-tooltip-arrow (image-top &optional timeout)
+ (pdf-util-assert-pdf-window)
+ (when (floatp image-top)
+ (setq image-top
+ (round (* image-top (cdr (pdf-view-image-size))))))
+ (let* (x-gtk-use-system-tooltips ;allow for display property in tooltip
+ (dx (+ (or (car (window-margins)) 0)
+ (car (window-fringes))))
+ (dy image-top)
+ (pos (list dx dy dx (+ dy (* 2 (frame-char-height)))))
+ (vscroll
+ (pdf-util-required-vscroll pos))
+ (tooltip-frame-parameters
+ `((border-width . 0)
+ (internal-border-width . 0)
+ ,@tooltip-frame-parameters))
+ (tooltip-hide-delay (or timeout 3)))
+ (when vscroll
+ (image-set-window-vscroll vscroll))
+ (setq dy (max 0 (- dy
+ (cdr (pdf-view-image-offset))
+ (window-vscroll nil t)
+ (frame-char-height))))
+ (when (overlay-get (pdf-view-current-overlay) 'before-string)
+ (let* ((e (window-inside-pixel-edges))
+ (xw (pdf-util-with-edges (e) e-width)))
+ (cl-incf dx (/ (- xw (car (pdf-view-image-size t))) 2))))
+ (pdf-util-tooltip-in-window
+ (propertize
+ " " 'display (propertize
+ "\u2192" ;;right arrow
+ 'display '(height 2)
+ 'face `(:foreground
+ "orange red"
+ :background
+ ,(if (bound-and-true-p pdf-view-midnight-minor-mode)
+ (cdr pdf-view-midnight-colors)
+ "white"))))
+ dx dy)))
+
+(defvar pdf-util--face-colors-cache (make-hash-table))
+
+(defadvice enable-theme (after pdf-util-clear-faces-cache activate)
+ (clrhash pdf-util--face-colors-cache))
+
+(defun pdf-util-face-colors (face &optional dark-p)
+ "Return both colors of FACE as a cons.
+
+Look also in inherited faces. If DARK-P is non-nil, return dark
+colors, otherwise light."
+ (let* ((bg (if dark-p 'dark 'light))
+ (spec (list (get face 'face-defface-spec)
+ (get face 'theme-face)
+ (get face 'customized-face)))
+ (cached (gethash face pdf-util--face-colors-cache)))
+ (cl-destructuring-bind (&optional cspec color-alist)
+ cached
+ (or (and color-alist
+ (equal cspec spec)
+ (cdr (assq bg color-alist)))
+ (let* ((this-bg (frame-parameter nil 'background-mode))
+ (frame-background-mode bg)
+ (f (and (not (eq bg this-bg))
+ (x-create-frame-with-faces '((visibility . nil))))))
+ (with-selected-frame (or f (selected-frame))
+ (unwind-protect
+ (let ((colors
+ (cons (face-attribute face :foreground nil 'default)
+ (face-attribute face :background nil 'default))))
+ (puthash face `(,(mapcar 'copy-sequence spec)
+ ((,bg . ,colors) ,@color-alist))
+ pdf-util--face-colors-cache)
+ colors)
+ (when (and f (frame-live-p f))
+ (delete-frame f)))))))))
+
+(defun pdf-util-window-attach (awindow &optional window)
+ "Attach AWINDOW to WINDOW.
+
+This has the following effect. Whenever WINDOW, defaulting to
+the selected window, stops displaying the buffer it currently
+displays (e.g., by switching buffers or because it was deleted)
+AWINDOW is deleted."
+ (unless window (setq window (selected-window)))
+ (let ((buffer (window-buffer window))
+ (hook (make-symbol "window-attach-hook")))
+ (fset hook
+ (lambda ()
+ (when (or (not (window-live-p window))
+ (not (eq buffer (window-buffer window))))
+ (remove-hook 'window-configuration-change-hook
+ hook)
+ ;; Deleting windows inside wcch may cause errors in
+ ;; windows.el .
+ (run-with-timer
+ 0 nil (lambda (win)
+ (when (and (window-live-p win)
+ (not (eq win (selected-window))))
+ (delete-window win)))
+ awindow))))
+ (add-hook 'window-configuration-change-hook hook)))
+
+(defun display-buffer-split-below-and-attach (buf alist)
+ "Display buffer action using `pdf-util-window-attach'."
+ (let ((window (selected-window))
+ (height (cdr (assq 'window-height alist)))
+ newwin)
+ (when height
+ (when (floatp height)
+ (setq height (round (* height (frame-height)))))
+ (setq height (- (max height window-min-height))))
+ (setq newwin (window--display-buffer
+ buf
+ (split-window-below height)
+ 'window alist))
+ (pdf-util-window-attach newwin window)
+ newwin))
+
+(defun pdf-util-goto-position (line &optional column)
+ "Goto LINE and COLUMN in the current buffer.
+
+COLUMN defaults to 0. Widen the buffer, if the position is
+outside the current limits."
+ (let ((pos
+ (when (> line 0)
+ (save-excursion
+ (save-restriction
+ (widen)
+ (goto-char 1)
+ (when (= 0 (forward-line (1- line)))
+ (when (and column (> column 0))
+ (forward-char (1- column)))
+ (point)))))))
+ (when pos
+ (when (or (< pos (point-min))
+ (> pos (point-max)))
+ (widen))
+ (goto-char pos))))
+
+(defun pdf-util-seq-alignment (seq1 seq2 &optional similarity-fn alignment-type)
+ "Return an alignment of sequences SEQ1 and SEQ2.
+
+SIMILARITY-FN should be a function. It is called with two
+arguments: One element from SEQ1 and one from SEQ2. It should
+return a number determining how similar the elements are, where
+higher values mean `more similar'. The default returns 1 if the
+elements are equal, else -1.
+
+ALIGNMENT-TYPE may be one of the symbols `prefix', `suffix',
+`infix' or nil. If it is `prefix', trailing elements in SEQ2 may
+be ignored. For example the alignment of
+
+\(0 1\) and \(0 1 2\)
+
+using prefix matching is 0, since the prefixes are equal and the
+trailing 2 is ignored. The other possible values have similar
+effects. The default is nil, which means to match the whole
+sequences.
+
+Return a cons \(VALUE . ALIGNMENT\), where VALUE says how similar
+the sequences are and ALIGNMENT is a list of \(E1 . E2\), where
+E1 is an element from SEQ1 or nil, likewise for E2. If one of
+them is nil, it means there is gap at this position in the
+respective sequence."
+
+ (cl-macrolet ((make-matrix (rows columns)
+ (list 'apply (list 'quote 'vector)
+ (list 'cl-loop 'for 'i 'from 1 'to rows
+ 'collect (list 'make-vector columns nil))))
+ (mset (matrix row column newelt)
+ (list 'aset (list 'aref matrix row) column newelt))
+ (mref (matrix row column)
+ (list 'aref (list 'aref matrix row) column)))
+ (let* ((nil-value nil)
+ (len1 (length seq1))
+ (len2 (length seq2))
+ (d (make-matrix (1+ len1) (1+ len2)))
+ (prefix-p (memq alignment-type '(prefix infix)))
+ (suffix-p (memq alignment-type '(suffix infix)))
+ (similarity-fn (or similarity-fn
+ (lambda (a b)
+ (if (equal a b) 1 -1)))))
+
+ (cl-loop for i from 0 to len1 do
+ (mset d i 0 (- i)))
+ (cl-loop for j from 0 to len2 do
+ (mset d 0 j (if suffix-p 0 (- j))))
+
+ (cl-loop for i from 1 to len1 do
+ (cl-loop for j from 1 to len2 do
+ (let ((max (max
+ (1- (mref d (1- i) j))
+ (+ (mref d i (1- j))
+ (if (and prefix-p (= i len1)) 0 -1))
+ (+ (mref d (1- i) (1- j))
+ (funcall similarity-fn
+ (elt seq1 (1- i))
+ (elt seq2 (1- j)))))))
+ (mset d i j max))))
+
+ (let ((i len1)
+ (j len2)
+ alignment)
+ (while (or (> i 0)
+ (> j 0))
+ (cond
+ ((and (> i 0)
+ (= (mref d i j)
+ (1- (mref d (1- i) j))))
+ (cl-decf i)
+ (push (cons (elt seq1 i) nil-value) alignment))
+ ((and (> j 0)
+ (= (mref d i j)
+ (+ (mref d i (1- j))
+ (if (or (and (= i 0) suffix-p)
+ (and (= i len1) prefix-p))
+ 0 -1))))
+ (cl-decf j)
+ (push (cons nil-value (elt seq2 j)) alignment))
+ (t
+ (cl-assert (and (> i 0) (> j 0)) t)
+ (cl-decf i)
+ (cl-decf j)
+ (push (cons (elt seq1 i)
+ (elt seq2 j)) alignment))))
+ (cons (mref d len1 len2) alignment)))))
+
+
+(defun pdf-util-pcre-quote (string)
+ "Escape STRING for use as a PCRE.
+
+See also `regexp-quote'."
+
+ (let ((to-escape
+ (eval-when-compile (append "\0\\|()[]{}^$*+?." nil)))
+ (chars (append string nil))
+ escaped)
+ (dolist (ch chars)
+ (when (memq ch to-escape)
+ (push ?\\ escaped))
+ (push ch escaped))
+ (apply 'string (nreverse escaped))))
+
+(defun pdf-util-frame-ppi ()
+ "Return the PPI of the current frame."
+ (let* ((props (frame-monitor-attributes))
+ (px (nthcdr 2 (alist-get 'geometry props)))
+ (mm (alist-get 'mm-size props))
+ (dp (sqrt (+ (expt (nth 0 px) 2)
+ (expt (nth 1 px) 2))))
+ (di (sqrt (+ (expt (/ (nth 0 mm) 25.4) 2)
+ (expt (/ (nth 1 mm) 25.4) 2)))))
+ (/ dp di)))
+
+(defvar pdf-view-use-scaling)
+
+(defun pdf-util-frame-scale-factor ()
+ "Return the frame scale factor depending on the image type used for display.
+When `pdf-view-use-scaling' is non-nil and imagemagick or
+image-io are used as the image type for display, return the
+backing-scale-factor of the frame if available. If a
+backing-scale-factor attribute isn't available, return 2 if the
+frame's PPI is larger than 180. Otherwise, return 1."
+ (if (and pdf-view-use-scaling
+ (memq (pdf-view-image-type) '(imagemagick image-io))
+ (fboundp 'frame-monitor-attributes))
+ (or (cdr (assq 'backing-scale-factor (frame-monitor-attributes)))
+ (if (>= (pdf-util-frame-ppi) 180)
+ 2
+ 1))
+ 1))
+
+
+;; * ================================================================== *
+;; * Imagemagick's convert
+;; * ================================================================== *
+
+(defcustom pdf-util-convert-program
+ ;; Avoid using the MS Windows command convert.exe .
+ (unless (memq system-type '(ms-dos windows-nt))
+ (executable-find "convert"))
+ "Absolute path to the convert program."
+ :group 'pdf-tools
+ :type 'executable)
+
+(defcustom pdf-util-fast-image-format nil
+ "An image format appropriate for fast displaying.
+
+This should be a cons \(TYPE . EXT\) where type is the Emacs
+image-type and EXT the appropriate file extension starting with a
+dot. If nil, the value is determined automatically.
+
+Different formats have different properties, with respect to
+Emacs loading time, convert creation time and the file-size. In
+general, uncompressed formats are faster, but may need a fair
+amount of (temporary) disk space."
+ :group 'pdf-tools
+ :type '(cons symbol string))
+
+(defun pdf-util-assert-convert-program ()
+ (unless (and pdf-util-convert-program
+ (file-executable-p pdf-util-convert-program))
+ (error "The pdf-util-convert-program is unset or non-executable")))
+
+(defun pdf-util-image-file-size (image-file)
+ "Determine the size of the image in IMAGE-FILE.
+
+Returns a cons \(WIDTH . HEIGHT\)."
+ (pdf-util-assert-convert-program)
+ (with-temp-buffer
+ (when (save-excursion
+ (= 0 (call-process
+ pdf-util-convert-program
+ nil (current-buffer) nil
+ image-file "-format" "%w %h" "info:")))
+ (let ((standard-input (current-buffer)))
+ (cons (read) (read))))))
+
+(defun pdf-util-convert (in-file out-file &rest spec)
+ "Convert image IN-FILE to OUT-FILE according to SPEC.
+
+IN-FILE should be the name of a file containing an image. Write
+the result to OUT-FILE. The extension of this filename usually
+determines the resulting image-type.
+
+SPEC is a property list, specifying what the convert program
+should do with the image. All manipulations operate on a
+rectangle, see below.
+
+SPEC may contain the following keys, respectively values.
+
+`:foreground' Set foreground color for all following operations.
+
+`:background' Dito, for the background color.
+
+`:commands' A list of strings representing arguments to convert
+for image manipulations. It may contain %-escape characters, as
+follows.
+
+%f -- Expands to the foreground color.
+%b -- Expands to the background color.
+%g -- Expands to the geometry of the current rectangle, i.e. WxH+X+Y.
+%x -- Expands to the left edge of rectangle.
+%X -- Expands to the right edge of rectangle.
+%y -- Expands to the top edge of rectangle.
+%Y -- Expands to the bottom edge of rectangle.
+%w -- Expands to the width of rectangle.
+%h -- Expands to the height of rectangle.
+
+Keep in mind, that every element of this list is seen by convert
+as a single argument.
+
+`:formats' An alist of additional %-escapes. Every element
+should be a cons \(CHAR . STRING\) or \(CHAR . FUNCTION\). In
+the first case, all occurrences of %-CHAR in the above commands
+will be replaced by STRING. In the second case FUNCTION is
+called with the current rectangle and it should return the
+replacement string.
+
+`:apply' A list of rectangles \(\(LEFT TOP RIGHT BOT\) ...\) in
+IN-FILE coordinates. Each such rectangle triggers one execution
+of the last commands given earlier in SPEC. E.g. a call like
+
+\(pdf-util-convert
+ image-file out-file
+ :foreground \"black\"
+ :background \"white\"
+ :commands '\(\"-fill\" \"%f\" \"-draw\" \"rectangle %x,%y,%X,%Y\"\)
+ :apply '\(\(0 0 10 10\) \(10 10 20 20\)\)
+ :commands '\(\"-fill\" \"%b\" \"-draw\" \"rectangle %x,%y,%X,%Y\"\)
+ :apply '\(\(10 0 20 10\) \(0 10 10 20\)\)\)
+
+would draw a 4x4 checkerboard pattern in the left corner of the
+image, while leaving the rest of it as it was.
+
+Returns OUT-FILE.
+
+See url `http://www.imagemagick.org/script/convert.php'."
+ (pdf-util-assert-convert-program)
+ (let* ((cmds (pdf-util-convert--create-commands spec))
+ (status (apply 'call-process
+ pdf-util-convert-program nil
+ (get-buffer-create "*pdf-util-convert-output*")
+ nil
+ `(,in-file ,@cmds ,out-file))))
+ (unless (and (numberp status) (= 0 status))
+ (error "The convert program exited with error status: %s" status))
+ out-file))
+
+(defun pdf-util-convert-asynch (in-file out-file &rest spec-and-callback)
+ "Like `pdf-util-convert', but asynchronous.
+
+If the last argument is a function, it is installed as the
+process sentinel.
+
+Returns the convert process."
+ (pdf-util-assert-convert-program)
+ (let ((callback (car (last spec-and-callback)))
+ spec)
+ (if (functionp callback)
+ (setq spec (butlast spec-and-callback))
+ (setq spec spec-and-callback
+ callback nil))
+ (let* ((cmds (pdf-util-convert--create-commands spec))
+ (proc
+ (apply 'start-process "pdf-util-convert"
+ (get-buffer-create "*pdf-util-convert-output*")
+ pdf-util-convert-program
+ `(,in-file ,@cmds ,out-file))))
+ (when callback
+ (set-process-sentinel proc callback))
+ proc)))
+
+(defun pdf-util-convert-page (&rest specs)
+ "Convert image of current page according to SPECS.
+
+Return the converted PNG image as a string. See also
+`pdf-util-convert'."
+
+ (pdf-util-assert-pdf-window)
+ (let ((in-file (make-temp-file "pdf-util-convert" nil ".png"))
+ (out-file (make-temp-file "pdf-util-convert" nil ".png")))
+ (unwind-protect
+ (let ((image-data
+ (plist-get (cdr (pdf-view-current-image)) :data)))
+ (with-temp-file in-file
+ (set-buffer-multibyte nil)
+ (set-buffer-file-coding-system 'binary)
+ (insert image-data))
+ (pdf-util-munch-file
+ (apply 'pdf-util-convert
+ in-file out-file specs)))
+ (when (file-exists-p in-file)
+ (delete-file in-file))
+ (when (file-exists-p out-file)
+ (delete-file out-file)))))
+
+
+(defun pdf-util-convert--create-commands (spec)
+ (let ((fg "red")
+ (bg "red")
+ formats result cmds s)
+ (while (setq s (pop spec))
+ (unless spec
+ (error "Missing value in convert spec:%s" (cons s spec)))
+ (cl-case s
+ (:foreground
+ (setq fg (pop spec)))
+ (:background
+ (setq bg (pop spec)))
+ (:commands
+ (setq cmds (pop spec)))
+ (:formats
+ (setq formats (append formats (pop spec) nil)))
+ (:apply
+ (dolist (m (pop spec))
+ (pdf-util-with-edges (m)
+ (let ((alist (append
+ (mapcar (lambda (f)
+ (cons (car f)
+ (if (stringp (cdr f))
+ (cdr f)
+ (funcall (cdr f) m))))
+ formats)
+ `((?g . ,(format "%dx%d+%d+%d"
+ m-width m-height
+ m-left m-top))
+ (?x . ,m-left)
+ (?X . ,m-right)
+ (?y . ,m-top)
+ (?Y . ,m-bot)
+ (?w . ,(- m-right m-left))
+ (?h . ,(- m-bot m-top))
+ (?f . ,fg)
+ (?b . ,bg)))))
+ (dolist (fmt cmds)
+ (push (format-spec fmt alist) result))))))))
+ (nreverse result)))
+
+;; FIXME: Check code below and document.
+
+(defun pdf-util-edges-p (obj &optional relative-p)
+ "Return non-nil, if OBJ look like edges.
+
+If RELATIVE-P is non-nil, also check that all values <= 1."
+
+ (and (consp obj)
+ (ignore-errors (= 4 (length obj)))
+ (cl-every (lambda (x)
+ (and (numberp x)
+ (>= x 0)
+ (or (null relative-p)
+ (<= x 1))))
+ obj)))
+
+(defun pdf-util-edges-empty-p (edges)
+ "Return non-nil, if EDGES area is empty."
+ (pdf-util-with-edges (edges)
+ (or (<= edges-width 0)
+ (<= edges-height 0))))
+
+(defun pdf-util-edges-inside-p (edges pos &optional epsilon)
+ (pdf-util-edges-contained-p
+ edges
+ (list (car pos) (cdr pos) (car pos) (cdr pos))
+ epsilon))
+
+(defun pdf-util-edges-contained-p (edges contained &optional epsilon)
+ (unless epsilon (setq epsilon 0))
+ (pdf-util-with-edges (edges contained)
+ (and (<= (- edges-left epsilon)
+ contained-left)
+ (>= (+ edges-right epsilon)
+ contained-right)
+ (<= (- edges-top epsilon)
+ contained-top)
+ (>= (+ edges-bot epsilon)
+ contained-bot))))
+
+(defun pdf-util-edges-intersection (e1 e2)
+ (pdf-util-with-edges (edges1 e1 e2)
+ (let ((left (max e1-left e2-left))
+ (top (max e1-top e2-top))
+ (right (min e1-right e2-right))
+ (bot (min e1-bot e2-bot)))
+ (when (and (<= left right)
+ (<= top bot))
+ (list left top right bot)))))
+
+(defun pdf-util-edges-union (&rest edges)
+ (if (null (cdr edges))
+ (car edges)
+ (list (apply 'min (mapcar 'car edges))
+ (apply 'min (mapcar 'cadr edges))
+ (apply 'max (mapcar 'cl-caddr edges))
+ (apply 'max (mapcar 'cl-cadddr edges)))))
+
+(defun pdf-util-edges-intersection-area (e1 e2)
+ (let ((inters (pdf-util-edges-intersection e1 e2)))
+ (if (null inters)
+ 0
+ (pdf-util-with-edges (inters)
+ (* inters-width inters-height)))))
+
+(defun pdf-util-read-image-position (prompt)
+ "Read a image position using prompt.
+
+Return the event position object."
+ (save-selected-window
+ (let ((ev (pdf-util-read-click-event
+ (propertize prompt 'face 'minibuffer-prompt)))
+ (buffer (current-buffer)))
+ (unless (mouse-event-p ev)
+ (error "Not a mouse event"))
+ (let ((posn (event-start ev)))
+ (unless (and (eq (window-buffer
+ (posn-window posn))
+ buffer)
+ (eq 'image (car-safe (posn-object posn))))
+ (error "Invalid image position"))
+ posn))))
+
+(defun pdf-util-read-click-event (&optional prompt seconds)
+ (let ((down (read-event prompt seconds)))
+ (unless (and (mouse-event-p down)
+ (equal (event-modifiers down)
+ '(down)))
+ (error "No a mouse click event"))
+ (let ((up (read-event prompt seconds)))
+ (unless (and (mouse-event-p up)
+ (equal (event-modifiers up)
+ '(click)))
+ (error "No a mouse click event"))
+ up)))
+
+(defun pdf-util-image-map-mouse-event-proxy (event)
+ "Set POS-OR-AREA in EVENT to 1 and unread it."
+ (interactive "e")
+ (setcar (cdr (cadr event)) 1)
+ (setq unread-command-events (list event)))
+
+(defun pdf-util-image-map-divert-mouse-clicks (id &optional buttons)
+ (dolist (kind '("" "down-" "drag-"))
+ (dolist (b (or buttons '(2 3 4 5 6)))
+ (local-set-key
+ (vector id (intern (format "%smouse-%d" kind b)))
+ 'pdf-util-image-map-mouse-event-proxy))))
+
+(defmacro pdf-util-do-events (event-resolution-unread-p condition &rest body)
+ "Read EVENTs while CONDITION executing BODY.
+
+Process at most 1/RESOLUTION events per second. If UNREAD-p is
+non-nil, unread the final non-processed event.
+
+\(FN (EVENT RESOLUTION &optional UNREAD-p) CONDITION &rest BODY\)"
+ (declare (indent 2) (debug ((symbolp form &optional form) form body)))
+ (cl-destructuring-bind (event resolution &optional unread-p)
+ event-resolution-unread-p
+ (let ((*seconds (make-symbol "seconds"))
+ (*timestamp (make-symbol "timestamp"))
+ (*clock (make-symbol "clock"))
+ (*unread-p (make-symbol "unread-p"))
+ (*resolution (make-symbol "resolution")))
+ `(let* ((,*unread-p ,unread-p)
+ (,*resolution ,resolution)
+ (,*seconds 0)
+ (,*timestamp (float-time))
+ (,*clock (lambda (&optional secs)
+ (when secs
+ (setq ,*seconds secs
+ ,*timestamp (float-time)))
+ (- (+ ,*timestamp ,*seconds)
+ (float-time))))
+ (,event (read-event)))
+ (while ,condition
+ (when (<= (funcall ,*clock) 0)
+ (progn ,@body)
+ (setq ,event nil)
+ (funcall ,*clock ,*resolution))
+ (setq ,event
+ (or (read-event nil nil
+ (and ,event
+ (max 0 (funcall ,*clock))))
+ ,event)))
+ (when (and ,*unread-p ,event)
+ (setq unread-command-events
+ (append unread-command-events
+ (list ,event))))))))
+
+(defmacro pdf-util-track-mouse-dragging (event-resolution &rest body)
+ "Read mouse movement events executing BODY.
+
+See also `pdf-util-do-events'.
+
+This macro should be used inside a command bound to a down-mouse
+event. It evaluates to t, if at least one event was processed in
+BODY, otherwise nil. In the latter case, the only event (usually
+a mouse click event) is unread.
+
+\(FN (EVENT RESOLUTION) &rest BODY\)"
+ (declare (indent 1) (debug ((symbolp form) body)))
+ (let ((ran-once-p (make-symbol "ran-once-p")))
+ `(let (,ran-once-p)
+ (track-mouse
+ (pdf-util-do-events (,@event-resolution t)
+ (mouse-movement-p ,(car event-resolution))
+ (setq ,ran-once-p t)
+ ,@body))
+ (when (and ,ran-once-p
+ unread-command-events)
+ (setq unread-command-events
+ (butlast unread-command-events)))
+ ,ran-once-p)))
+
+(defun pdf-util-remove-duplicates (list)
+ "Remove duplicates from LIST stably using `equal'."
+ (let ((ht (make-hash-table :test 'equal))
+ result)
+ (dolist (elt list (nreverse result))
+ (unless (gethash elt ht)
+ (push elt result)
+ (puthash elt t ht)))))
+
+(provide 'pdf-util)
+
+;;; pdf-util.el ends here
Copyright 2019--2024 Marius PETER