|
|
- ;;; 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.
-
- Singal 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
|