;;; preview.el --- embed preview LaTeX images in source buffer  -*- lexical-binding: t; -*-

;; Copyright (C) 2001-2021  Free Software Foundation, Inc.

;; Author: David Kastrup
;; Keywords: tex, wp, convenience

;; This file 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, or (at your option)
;; any later version.

;; This file 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 GNU Emacs; see the file COPYING.  If not, write to
;; the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
;; Boston, MA 02110-1301, USA.

;;; Commentary:

;; This style is for the "seamless" embedding of generated images
;; into LaTeX source code.  Please see the README and INSTALL files
;; for further instruction.
;;
;; Please use the usual configure script for installation: more than
;; just Elisp files are involved: a LaTeX style, icon files, startup
;; code and so on.
;;
;; Quite a few things with regard to preview-latex's operation can be
;; configured by using
;; M-x customize-group RET preview RET
;;
;; Please report bugs with M-x preview-report-bug RET.

;;; Code:

(require 'tex-site)
(require 'tex)
(require 'tex-buf)
(require 'latex)

(eval-when-compile
  (condition-case nil
      (require 'desktop)
    (file-error (message "Missing desktop package:
preview-latex buffers will not survive across sessions.")))
  (condition-case nil
      (require 'reporter)
    (file-error (message "Missing reporter library, probably from the mail-lib package:
preview-latex's bug reporting commands will probably not work.")))
  (require 'info))

(defgroup preview nil "Embed Preview images into LaTeX buffers."
  :group 'AUCTeX
  :prefix "preview-"
  :link '(custom-manual "(preview-latex)Top")
  :link '(info-link "(preview-latex)The Emacs interface")
  :link '(url-link :tag "Homepage" "https://www.gnu.org/software/auctex/"))

(defgroup preview-gs nil "Preview's Ghostscript renderer."
  :group 'preview
  :prefix "preview-")

(defgroup preview-appearance nil "Preview image appearance."
  :group 'preview
  :prefix "preview-")

(defconst preview-specs-type
  '(repeat
    (list :tag "Image spec"
          ;; Use an extra :value keyword to avoid a bug in
          ;; `widget-convert' of XEmacs 21.4 and Emacs 21.
          ;; Analogously for the following `const' statements.
          (const :format "" :value :type)
          (choice :tag "Image type"
                  (const xpm)
                  (const xbm)
                  (symbol :tag "Other"))
          (set :inline t :tag "Minimum font size"
               (list :inline t :tag ""
                     (const :format "" :value :min)
                     (integer :tag "pixels")))
          (const :format "" :value :file) (string :tag "Filename")
          (set :inline t :tag "Ascent ratio"
               (list :inline t :tag ""
                     (const :format "" :value :ascent)
                     (integer :tag "percent of image"
                              :value 50))))))

(defun preview-specs-setter (symbol value)
  "Set SYMBOL to VALUE and clear `preview-min-alist' property.
This is used in icon specs, so that customizing will
clear cached icons."
  (put symbol 'preview-min-alist nil)
  (set-default symbol value))

(defcustom preview-nonready-icon-specs
  '((:type xpm :min 26 :file "prvwrk24.xpm" :ascent 90)
    (:type xpm :min 22 :file "prvwrk20.xpm" :ascent 90)
    (:type xpm :min 17 :file "prvwrk16.xpm" :ascent 90)
    (:type xpm :min 15 :file "prvwrk14.xpm" :ascent 90)
    (:type xpm         :file "prvwrk12.xpm" :ascent 90)
    (:type xbm         :file "prvwrk24.xbm" :ascent 90))
  "The icon used for previews to be generated.
The spec must begin with `:type'.  File names are relative to
`load-path' and `data-directory', a spec `:min' requires a
minimal pixel height for `preview-reference-face' before the spec
will be considered.  Since evaluating the `:file' spec takes
considerable time under XEmacs, it should come after the `:min'
spec to avoid unnecessary evaluation time."
  :group 'preview-appearance
  :type preview-specs-type
  :set #'preview-specs-setter)

(defvar preview-nonready-icon nil
  "The icon used for previews to be generated.
Suitable spec is chosen from `preview-nonready-icon-specs'.")

(defcustom preview-error-icon-specs
  '((:type xpm :min 22 :file "prverr24.xpm" :ascent 90)
    (:type xpm :min 18 :file "prverr20.xpm" :ascent 90)
    (:type xpm         :file "prverr16.xpm" :ascent 90)
    (:type xbm         :file "prverr24.xbm" :ascent 90))
  "The icon used for PostScript errors.
The spec must begin with `:type'.  File names are relative to
`load-path' and `data-directory', a spec `:min' requires a
minimal pixel height for `preview-reference-face' before the spec
will be considered.  Since evaluating the `:file' spec takes
considerable time under XEmacs, it should come after the `:min'
spec to avoid unnecessary evaluation time."
  :group 'preview-appearance
  :type preview-specs-type
  :set #'preview-specs-setter
)

(defvar preview-error-icon nil
  "The icon used for PostScript errors.
Suitable spec is chosen from `preview-error-icon-specs'.")

(defcustom preview-icon-specs
  '((:type xpm :min 24 :file "prvtex24.xpm" :ascent 75)
    (:type xpm :min 20 :file "prvtex20.xpm" :ascent 75)
    (:type xpm :min 16 :file "prvtex16.xpm" :ascent 75)
    (:type xpm         :file "prvtex12.xpm" :ascent 75)
    (:type xbm :min 24 :file "prvtex24.xbm" :ascent 75)
    (:type xbm :min 16 :file "prvtex16.xbm" :ascent 75)
    (:type xbm         :file "prvtex12.xbm" :ascent 75))
  "The icon used for an open preview.
The spec must begin with `:type'.  File names are relative to
`load-path' and `data-directory', a spec `:min' requires a
minimal pixel height for `preview-reference-face' before the spec
will be considered.  Since evaluating the `:file' spec takes
considerable time under XEmacs, it should come after the `:min'
spec to avoid unnecessary evaluation time."
  :group 'preview-appearance
  :type preview-specs-type
  :set #'preview-specs-setter)

(defvar preview-icon nil
  "The icon used for an open preview.
Suitable spec is chosen from `preview-icon-specs'.")

(defgroup preview-latex nil "LaTeX options for preview."
  :group 'preview
  :prefix "preview-")

(defcustom preview-image-creators
  '((dvipng
     (open preview-gs-open preview-dvipng-process-setup)
     (place preview-gs-place)
     (close preview-dvipng-close))
    (png (open preview-gs-open)
         (place preview-gs-place)
         (close preview-gs-close))
    (jpeg (open preview-gs-open)
          (place preview-gs-place)
          (close preview-gs-close))
    (pnm (open preview-gs-open)
          (place preview-gs-place)
          (close preview-gs-close))
    (tiff (open preview-gs-open)
          (place preview-gs-place)
          (close preview-gs-close)))
  "Define functions for generating images.
These functions get called in the process of generating inline
images of the specified type.  The open function is called
at the start of a rendering pass, the place function for
placing every image, the close function at the end of
the pass.  Look at the documentation of the various
functions used here for the default settings, and at
the function `preview-call-hook' through which those are
called.  Additional argument lists specified in here
are passed to the functions before any additional
arguments given to `preview-call-hook'.

Not all of these image types may be supported by your copy
of Ghostscript, or by your copy of Emacs."
  :group 'preview-gs
  :type '(alist :key-type (symbol :tag "Preview's image type")
                :value-type
                (alist :tag "Handler" :key-type (symbol :tag "Operation:")
                       :value-type (list :tag "Handler"
                                         (function :tag "Handler function")
                                         (repeat :tag "Additional \
function args" :inline t sexp))
                       :options (open place close))))

(defcustom preview-gs-image-type-alist
  '((png png "-sDEVICE=png16m")
    (dvipng png "-sDEVICE=png16m")
    (jpeg jpeg "-sDEVICE=jpeg")
    (pnm pbm "-sDEVICE=pnmraw")
    (tiff tiff "-sDEVICE=tiff12nc"))
  "Alist of image types and corresponding Ghostscript options.
The `dvipng' and `postscript' (don't use) entries really specify
a fallback device when images can't be processed by the requested
method, like when PDFTeX was used."
  :group 'preview-gs
  :type '(repeat (list :tag nil (symbol :tag "preview image-type")
                       (symbol :tag "Emacs image-type")
                       (repeat :inline t :tag "Ghostscript options" string))))

(defcustom preview-image-type 'png
  "Image type to be used in images."
  :group 'preview-gs
  :type (append '(choice)
                (mapcar (lambda (symbol) (list 'const (car symbol)))
                        preview-image-creators)
                '((symbol :tag "Other"))))

(defun preview-call-hook (symbol &rest rest)
  "Call a function from `preview-image-creators'.
This looks up SYMBOL in the `preview-image-creators' entry
for the image type `preview-image-type' and calls the
hook function given there with the arguments specified there
followed by REST.  If such a function is specified in there,
that is."
  (let ((hook (cdr (assq symbol
                    (cdr (assq preview-image-type
                               preview-image-creators))))))
    (when hook
      (apply (car hook) (append (cdr hook) rest)))))


(defvar TeX-active-tempdir nil
  "List of directory name, top directory name and reference count.")
(make-variable-buffer-local 'TeX-active-tempdir)

(defcustom preview-bb-filesize 1024
  "Size of file area scanned for bounding box information."
  :group 'preview-gs :type 'integer)

(defcustom preview-preserve-indentation t
  "Whether to keep additional whitespace at the left of a line."
  :group 'preview-appearance :type 'boolean)

(defun preview-extract-bb (filename)
  "Extract EPS bounding box vector from FILENAME."
  (with-temp-buffer
    (insert-file-contents-literally filename nil 0 preview-bb-filesize
                                    t)
    (goto-char (point-min))
    (when (search-forward-regexp "%%BoundingBox:\
 +\\([-+]?[0-9.]+\\)\
 +\\([-+]?[0-9.]+\\)\
 +\\([-+]?[0-9.]+\\)\
 +\\([-+]?[0-9.]+\\)" nil t)
      (vector
       (if preview-preserve-indentation
           (min 72 (string-to-number (match-string 1)))
         (string-to-number (match-string 1)))
       (string-to-number (match-string 2))
       (string-to-number (match-string 3))
       (string-to-number (match-string 4))
       ))))

(defcustom preview-prefer-TeX-bb nil
  "Prefer TeX bounding box to EPS one if available.
If `preview-fast-conversion' is set, this option is not
 consulted since the TeX bounding box has to be used anyway."
  :group 'preview-gs
  :type 'boolean)

(defcustom preview-TeX-bb-border 0.5
  "Additional space in pt around Bounding Box from TeX."
  :group 'preview-gs
  :type 'number)

(defvar preview-parsed-font-size nil
  "Font size as parsed from the log of LaTeX run.")
(make-variable-buffer-local 'preview-parsed-font-size)
(defvar preview-parsed-magnification nil
  "Magnification as parsed from the log of LaTeX run.")
(make-variable-buffer-local 'preview-parsed-magnification)
(defvar preview-parsed-pdfoutput nil
  "PDFoutput as parsed from the log of LaTeX run.")
(make-variable-buffer-local 'preview-parsed-pdfoutput)
(defvar preview-parsed-counters nil
  "Counters as parsed from the log of LaTeX run.")
(make-variable-buffer-local 'preview-parsed-counters)
(defvar preview-parsed-tightpage nil
  "Tightpage as parsed from the log of LaTeX run.")
(make-variable-buffer-local 'preview-parsed-tightpage)

(defun preview-get-magnification ()
  "Get magnification from `preview-parsed-magnification'."
  (if preview-parsed-magnification
      (/ preview-parsed-magnification 1000.0) 1.0))

(defun preview-TeX-bb (list)
  "Calculate bounding box from (ht dp wd).
LIST consists of TeX dimensions in sp (1/65536 TeX point)."
  (and
   (consp list)
   (let* ((dims (vconcat (mapcar
                          #'(lambda (x)
                              (/ x 65781.76))
                          list)))
          (box
           (vector
            (+ 72 (min 0 (aref dims 2)))
            (+ 720 (min (aref dims 0) (- (aref dims 1)) 0))
            (+ 72 (max 0 (aref dims 2)))
            (+ 720 (max (aref dims 0) (- (aref dims 1)) 0))))
          (border (if preview-parsed-tightpage
                      (vconcat (mapcar
                                #'(lambda(x)
                                    (/ x 65781.76))
                                preview-parsed-tightpage))
                    (vector (- preview-TeX-bb-border)
                            (- preview-TeX-bb-border)
                            preview-TeX-bb-border
                            preview-TeX-bb-border))))
     (dotimes (i 4)
       (aset box i (+ (aref box i) (aref border i))))
     box)))

(defcustom preview-gs-command
  (or ;; The GS wrapper coming with TeX Live
      (executable-find "rungs")
      ;; The MikTeX builtin GS
      (let ((gs (executable-find "mgs")))
        ;; Check if mgs is functional for external non-MikTeX apps.
        ;; See http://blog.miktex.org/post/2005/04/07/Starting-mgsexe-at-the-DOS-Prompt.aspx
        (when (and gs (= 0 (shell-command (concat (shell-quote-argument gs) " -q -dNODISPLAY -c quit"))))
          gs))
      ;; Windows ghostscript
      (executable-find "GSWIN32C.EXE")
      ;; standard GhostScript
      (executable-find "gs"))
  "How to call gs for conversion from EPS.  See also `preview-gs-options'."
  :group 'preview-gs
  :type 'string)

(defcustom preview-gs-options '("-q" "-dDELAYSAFER" "-dNOPAUSE"
                                "-DNOPLATFONTS" "-dPrinted"
                                "-dTextAlphaBits=4"
                                "-dGraphicsAlphaBits=4")
  "Options with which to call gs for conversion from EPS.
See also `preview-gs-command'."
  :group 'preview-gs
  :type '(repeat string))

(defvar preview-gs-queue nil
  "List of overlays to convert using gs.
Buffer-local to the appropriate TeX process buffer.")
(make-variable-buffer-local 'preview-gs-queue)

(defvar preview-gs-outstanding nil
  "Overlays currently processed.")
(make-variable-buffer-local 'preview-gs-outstanding)

(defcustom preview-gs-outstanding-limit 2
  "Number of requests allowed to be outstanding.
This is the number of not-yet-completed requests we
might at any time have piped into Ghostscript.  If
this number is larger, the probability of Ghostscript
working continuously is higher when Emacs is rather
busy.  If this number is smaller, redisplay will
follow changes in the displayed buffer area faster."
  :group 'preview-gs
  :type '(restricted-sexp
          :match-alternatives
          ((lambda (value) (and
                            (integerp value)
                            (> value 0)
                            (< value 10))))
          :tag "small number"))

(defvar preview-gs-answer nil
  "Accumulated answer of Ghostscript process.")
(make-variable-buffer-local 'preview-gs-answer)

(defvar preview-gs-image-type nil
  "Image type for gs produced images.")
(make-variable-buffer-local 'preview-gs-image-type)

(defvar preview-gs-sequence nil
  "Pair of sequence numbers for gs produced images.")
(make-variable-buffer-local 'preview-gs-sequence)

(defvar preview-scale nil
  "Screen scale of images.
Magnify by this factor to make images blend with other
screen content.  Buffer-local to rendering buffer.")
(make-variable-buffer-local 'preview-scale)

(defvar preview-colors nil
  "Color setup list.
An array with elements 0, 1 and 2 for background,
foreground and border colors, respectively.  Each element
is a list of 3 real numbers between 0 and 1, or NIL
of nothing special should be done for the color")
(make-variable-buffer-local 'preview-colors)

(defvar preview-gs-init-string nil
  "Ghostscript setup string.")
(make-variable-buffer-local 'preview-gs-init-string)

(defvar preview-ps-file nil
  "PostScript file name for fast conversion.")
(make-variable-buffer-local 'preview-ps-file)

(defvar preview-gs-dsc nil
  "Parsed DSC information.")
(make-variable-buffer-local 'preview-gs-dsc)

(defvar preview-resolution nil
  "Screen resolution where rendering started.
Cons-cell of x and y resolution, given in
dots per inch.  Buffer-local to rendering buffer.")
(make-variable-buffer-local 'preview-resolution)

(defun preview-gs-resolution (scale xres yres)
  "Generate resolution argument for gs.
Calculated from real-life factor SCALE and XRES and
YRES, the screen resolution in dpi."
  (format "-r%gx%g"
          (/ (* scale xres) (preview-get-magnification))
          (/ (* scale yres) (preview-get-magnification))))

(defun preview-gs-behead-outstanding (err)
  "Remove leading element of outstanding queue after error.
Return element if non-nil.  ERR is the error string to
show as response of Ghostscript."
  (let ((ov (pop preview-gs-outstanding)))
    (when ov
      (preview-gs-flag-error ov err)
      (overlay-put ov 'queued nil))
    ov))

(defvar preview-gs-command-line nil)
(make-variable-buffer-local 'preview-gs-command-line)
(defvar preview-gs-file nil)
(make-variable-buffer-local 'preview-gs-file)

(defcustom preview-fast-conversion t
  "Set this for single-file PostScript conversion.
This will have no effect when `preview-image-type' is
set to `postscript'."
  :group 'preview-latex
  :type 'boolean)

(defun preview-string-expand (arg &optional separator)
  "Expand ARG as a string.
It can already be a string.  Or it can be a list, then it is
recursively evaluated using SEPARATOR as separator.  If a list
element is in itself a CONS cell, the CAR of the list (after symbol
dereferencing) can evaluate to either a string, in which case it is
used as a separator for the rest of the list,
or a boolean (t or nil) in which case the rest of the list is
either evaluated and concatenated or ignored, respectively.
ARG can be a symbol, and so can be the CDR
of a cell used for string concatenation."
  (cond
   ((stringp arg) arg)
   ((consp arg)
    (mapconcat
     #'identity
     (delq nil
           (mapcar
            (lambda(x)
              (if (consp x)
                  (let ((sep (car x)))
                    (while (and (symbolp sep)
                                (not (memq sep '(t nil))))
                      (setq sep (symbol-value sep)))
                    (if (stringp sep)
                        (preview-string-expand (cdr x) sep)
                      (and sep
                           (preview-string-expand (cdr x)))))
                (preview-string-expand x)))
            arg))
     (or separator "")))
   ((and (symbolp arg) (not (memq arg '(t nil))))
    (preview-string-expand (symbol-value arg) separator))
   (t (error "Bad string expansion"))))

(defconst preview-expandable-string
  (let ((f (lambda (x)
             `(choice
               string
               (repeat :tag "Concatenate"
                       (choice
                        string
                        (cons :tag "Separated list"
                              (choice (string :tag "Separator")
                                      (symbol :tag
                                              "Indirect separator or flag"))
                              ,x)
                        (symbol :tag "Indirect variable (no separator)")))
               (symbol :tag "Indirect variable (with separator)")))))
    (funcall f (funcall f 'sexp)))
  "Type to be used for `preview-string-expand'.
Just a hack until we get to learn how to do this properly.
Recursive definitions are not popular with Emacs,
so we define this type just two levels deep.  This
kind of expandible string can either be just a string, or a
cons cell with a separator string in the CAR, and either
an explicit list of elements in the CDR, or a symbol to
be consulted recursively.")

(defcustom preview-dvipng-command
  "dvipng -picky -noghostscript %d -o %m/prev%%03d.png"
  "Command used for converting to separate PNG images.

You might specify options for converting to other image types,
but then you'll need to adapt `preview-dvipng-image-type'."
  :group 'preview-latex
  :type 'string)

(defcustom preview-dvipng-image-type
  'png
  "Image type that dvipng produces.

You'll need to change `preview-dvipng-command' too,
if you customize this."
  :group 'preview-latex
  :type '(choice (const png)
                 (const gif)
                 (symbol :tag "Other" :value png)))

(defcustom preview-dvips-command
  "dvips -Pwww -i -E %d -o %m/preview.000"
  "Command used for converting to separate EPS images."
  :group 'preview-latex
  :type 'string)

(defcustom preview-fast-dvips-command
  "dvips -Pwww %d -o %m/preview.ps"
  "Command used for converting to a single PS file."
  :group 'preview-latex
  :type 'string)

(defcustom preview-pdf2dsc-command
  "pdf2dsc %(O?pdf) %m/preview.dsc"
  "Command used for generating dsc from a PDF file."
  :group 'preview-latex
  :type 'string)

(defun preview-gs-queue-empty ()
  "Kill off everything remaining in `preview-gs-queue'."
  (mapc #'preview-delete preview-gs-outstanding)
  (dolist (ov preview-gs-queue)
    (if (overlay-get ov 'queued)
        (preview-delete ov)))
  (setq preview-gs-outstanding nil)
  (setq preview-gs-queue nil))

(defvar preview-error-condition nil
  "Last error raised and to be reported.")

(defun preview-log-error (err context &optional process)
  "Log an error message to run buffer.
ERR is the caught error syndrome, CONTEXT is where it
occured, PROCESS is the process for which the run-buffer
is to be used."
  (when (or (null process) (buffer-name (process-buffer process)))
    (with-current-buffer (or (and process
                                  (process-buffer process))
                             (current-buffer))
      (save-excursion
        (goto-char (or (and process
                            (process-buffer process)
                            (marker-buffer (process-mark process))
                            (process-mark process))
                       (point-max)))
        (insert-before-markers
         (format "%s: %s\n"
                 context (error-message-string err)))
        (display-buffer (current-buffer)))))
  (setq preview-error-condition err))

(defun preview-reraise-error (&optional process)
  "Raise an error that has been logged.
Makes sure that PROCESS is removed from the \"Compilation\"
tag in the mode line."
  (when preview-error-condition
    (unwind-protect
        (signal (car preview-error-condition) (cdr preview-error-condition))
      (setq preview-error-condition nil
            compilation-in-progress (delq process compilation-in-progress)))))

(defcustom preview-pdf-color-adjust-method t
  "Method to adjust colors of images generated from PDF.
It is not consulted when the latex command produces DVI files.

The valid values are:

t: preview-latex transfers the foreground and background colors
of Emacs to the generated images.  This option requires that
Ghostscript has working DELAYBIND feature, thus is invalid with
gs 9.27 (and possibly < 9.27).

`compatible': preview-latex uses another mothod to transfer
colors.  This option is provided for compatibility with older gs.
See the below explanation for detail.

nil: no adjustment is done and \"black on white\" image is
generated regardless of Emacs color.  This is provided for fallback for
gs 9.27 users with customized foreground color.  See the below
explanation for detail.

When the latex command produces PDF rather than DVI and Emacs has
non-trivial foreground color, the traditional method (`compatible')
makes gs >= 9.27 to stop with error.  Here, \"non-trivial foreground
color\" includes customized themes.

If you use such non-trivial foreground color and the version of
Ghostscript equals to 9.27, you have two options:

- Choose the value `compatible' and customize
`preview-reference-face' to have default (black) foreground
color.  This makes the generated image almost non-readable on
dark background, so the next option would be your only choice in
that case.
- Choose the value nil, which forces plain \"black on white\"
appearance for the generated image.  You can at least read what
are written in the image although they may not match with your
Emacs color well."
  :group 'preview-appearance
  :type '(choice
          (const :tag "Adjust to Emacs color (gs > 9.27)" t)
          (const :tag "Compatibility for gs =< 9.27" compatible)
          (const :tag "No adjustment (B/W, for gs 9.27)" nil)))

(defun preview-gs-sentinel (process string)
  "Sentinel function for rendering process.
Gets the default PROCESS and STRING arguments
and tries to restart Ghostscript if necessary."
  (condition-case err
      (let ((status (process-status process)))
        (when (memq status '(exit signal))
          (setq compilation-in-progress (delq process compilation-in-progress)))
        (when (buffer-name (process-buffer process))
          (with-current-buffer (process-buffer process)
            (goto-char (point-max))
            (insert-before-markers "\n" mode-name " " string)
            (forward-char -1)
            (insert " at "
                    (substring (current-time-string) 0 -5))
            (forward-char 1)
            (TeX-command-mode-line process)
            (when (memq status '(exit signal))
              ;; process died.
              ;;  Throw away culprit, go on.
              (let* ((err (concat preview-gs-answer "\n"
                                  (process-name process) " " string))
                     (ov (preview-gs-behead-outstanding err)))
                (when (and (null ov) preview-gs-queue)
                  (save-excursion
                    (goto-char (if (marker-buffer (process-mark process))
                                   (process-mark process)
                                 (point-max)))
                    (insert-before-markers err)))
                (delete-process process)
                (if (or (null ov)
                        (eq status 'signal))
                    ;; if process was killed explicitly by signal, or if nothing
                    ;; was processed, we give up on the matter altogether.
                    (progn
                      (when preview-ps-file
                        (condition-case nil
                            (preview-delete-file preview-ps-file)
                          (file-error nil)))
                      (preview-gs-queue-empty))

                  ;; restart only if we made progress since last call
                  (let (filenames)
                    (dolist (ov preview-gs-outstanding)
                      (setq filenames (overlay-get ov 'filenames))
                      (condition-case nil
                          (preview-delete-file (nth 1 filenames))
                        (file-error nil))
                      (setcdr filenames nil)))
                  (setq preview-gs-queue (nconc preview-gs-outstanding
                                                preview-gs-queue))
                  (setq preview-gs-outstanding nil)
                  (preview-gs-restart)))))))
    (error (preview-log-error err "Ghostscript" process)))
  (preview-reraise-error process))

(defun preview-gs-filter (process string)
  "Filter function for processing Ghostscript output.
Gets the usual PROCESS and STRING parameters, see
`set-process-filter' for a description."
  (with-current-buffer (process-buffer process)
    (setq preview-gs-answer (concat preview-gs-answer string))
    (while (string-match "GS\\(<[0-9]+\\)?>" preview-gs-answer)
      (let* ((pos (match-end 0))
             (answer (substring preview-gs-answer 0 pos)))
        (setq preview-gs-answer (substring preview-gs-answer pos))
        (condition-case err
            (preview-gs-transact process answer)
          (error (preview-log-error err "Ghostscript filter" process))))))
  (preview-reraise-error))

(defun preview-gs-restart ()
  "Start a new Ghostscript conversion process."
  (when preview-gs-queue
    (if preview-gs-sequence
        (setcar preview-gs-sequence (1+ (car preview-gs-sequence)))
      (setq preview-gs-sequence (list 1)))
    (setcdr preview-gs-sequence 1)
    (let* ((process-connection-type nil)
           (outfile (format "-sOutputFile=%s"
                            (file-relative-name
                             (format "%s/pr%d-%%d.%s"
                                     (car TeX-active-tempdir)
                                     (car preview-gs-sequence)
                                     preview-gs-image-type))))
           (process
            (apply #'start-process
                   "Preview-Ghostscript"
                   (current-buffer)
                   preview-gs-command
                   outfile
                   preview-gs-command-line)))
      (goto-char (point-max))
      (insert-before-markers "Running `Preview-Ghostscript' with ``"
                             (mapconcat #'shell-quote-argument
                                        (append
                                         (list preview-gs-command
                                               outfile)
                                         preview-gs-command-line)
                                        " ") "''\n")
      (setq preview-gs-answer "")
      (set-process-query-on-exit-flag process nil)
      (set-process-sentinel process #'preview-gs-sentinel)
      (set-process-filter process #'preview-gs-filter)
      (process-send-string process preview-gs-init-string)
      (setq mode-name "Preview-Ghostscript")
      (push process compilation-in-progress)
      (TeX-command-mode-line process)
      (force-mode-line-update)
      process)))

(defun preview-gs-open (&optional setup)
  "Start a Ghostscript conversion pass.
SETUP may contain a parser setup function."
  (let ((image-info (assq preview-image-type preview-gs-image-type-alist)))
    (setq preview-gs-image-type (nth 1 image-info))
    (setq preview-gs-sequence nil)
    (setq preview-gs-command-line (append
                                   preview-gs-options
                                   (nthcdr 2 image-info))
          preview-gs-init-string
          (format "{DELAYSAFER{.setsafe}if}stopped pop\
/.preview-BP currentpagedevice/BeginPage get dup \
null eq{pop{pop}bind}if def\
<</BeginPage{currentpagedevice/PageSize get dup 0 get 1 ne exch 1 get 1 ne or\
{.preview-BP %s}{pop}ifelse}bind/PageSize[1 1]>>setpagedevice\
/preview-do{/.preview-ST[count 4 roll save]def dup length 0 eq\
{pop}{setpagedevice}{ifelse exec}\
stopped{handleerror quit}if \
.preview-ST aload pop restore}bind def "
                  (preview-gs-color-string
                   preview-colors
                   ;; Compatibility for gs 9.27 with non-trivial
                   ;; foreground color and dark background.
                   ;; Suppress color adjustment with PDF backend
                   ;; when `preview-pdf-color-adjust-method' is nil.
                   (and (not preview-pdf-color-adjust-method)
                        ;; The switch `preview-parsed-pdfoutput' isn't
                        ;; set before parsing the latex output, so use
                        ;; heuristic here.
                        (with-current-buffer TeX-command-buffer
                          (and TeX-PDF-mode
                               (not (TeX-PDF-from-DVI))))))))
    (preview-gs-queue-empty)
    (preview-parse-messages (or setup #'preview-gs-dvips-process-setup))))

(defun preview-gs-color-value (value)
  "Return string to be used as color value for an RGB component.
Conversion from Emacs color numbers (0 to 65535) in VALUE
to Ghostscript floats."
  (format "%g" (/ value 65535.0)))

(defun preview-pdf-color-string (colors)
  "Return a string that patches PDF foreground color to work properly."
  (let ((fg (aref colors 1)))
    (if fg
        (cond ((eq preview-pdf-color-adjust-method t)
               ;; New code for gs > 9.27.
               ;; This assumes DELAYBIND feature, which is known to be
               ;; broken in gs 9.27 (and possibly, < 9.27).
               ;; <URL:https://lists.gnu.org/archive/html/auctex-devel/2019-07/msg00000.html>
               ;; DELAYBIND is sometimes mentioned in association with
               ;; security holes in the changelog of Ghostscript:
               ;; <URL:https://www.ghostscript.com/doc/9.27/History9.htm>
               ;; Thus we might have to be prepared for removal of this
               ;; feature in future Ghostscript.
               (concat
                "/initgraphics {
  //initgraphics
  /RG where {
    pop "
                (mapconcat #'preview-gs-color-value fg " ")
                " 3 copy rg RG
  } if
} bind def .bindnow "))
              ((eq preview-pdf-color-adjust-method 'compatible)
               ;; Traditional code for gs < 9.27.
               (concat
                "/GS_PDF_ProcSet GS_PDF_ProcSet dup maxlength dict copy dup begin\
/graphicsbeginpage{//graphicsbeginpage exec "
                (mapconcat #'preview-gs-color-value fg " ")
                " 3 copy rg RG}bind store end readonly store "))
              (;; Do nothing otherwise.
               t
               "")))))

(defun preview-gs-color-string (colors &optional suppress-fgbg)
  "Return a string setting up COLORS.
If optional argument SUPPRESS-FGBG is non-nil, behave as if FG/BG
colors were just the default value."
  (let ((bg (and (not suppress-fgbg)
                 (aref colors 0)))
        (fg (and (not suppress-fgbg)
                 (aref colors 1)))
        (mask (aref colors 2))
        (border (aref colors 3)))
    (concat
     (and (or (and mask border) (and bg (not fg)))
          "gsave ")
     (and bg
         (concat
          (mapconcat #'preview-gs-color-value bg " ")
          " setrgbcolor clippath fill "))
     (and mask border
         (format "%s setrgbcolor false setstrokeadjust %g \
setlinewidth clippath strokepath \
matrix setmatrix true \
{2 index{newpath}if round exch round exch moveto pop false}\
{round exch round exch lineto}{curveto}{closepath}\
pathforall pop fill "
                 (mapconcat #'preview-gs-color-value mask " ")
                 (* 2 border)))
          ;; I hate antialiasing.  Warp border to integral coordinates.
     (and (or (and mask border) (and bg (not fg)))
          "grestore ")
     (and fg
          (concat
           (mapconcat #'preview-gs-color-value fg " ")
           " setrgbcolor")))))

(defun preview-dvipng-color-string (colors res)
  "Return color setup tokens for dvipng.
Makes a string of options suitable for passing to dvipng.
Pure borderless black-on-white will return an empty string."
  (let
      ((bg (aref colors 0))
       (fg (aref colors 1))
       (mask (aref colors 2))
       (border (aref colors 3)))
    (concat
     (and bg
          (format "--bg \"rgb %s\" "
                  (mapconcat #'preview-gs-color-value bg " ")))
     (and fg
          (format "--fg \"rgb %s\" "
                  (mapconcat #'preview-gs-color-value fg " ")))
     (and mask border
          (format "--bd \"rgb %s\" "
                  (mapconcat #'preview-gs-color-value mask " ")))
     (and border
          (format "--bd %d" (max 1 (round (/ (* res border) 72.0))))))))

(defsubst preview-supports-image-type (imagetype)
  "Check if IMAGETYPE is supported."
  (image-type-available-p imagetype))

(defun preview-gs-dvips-process-setup ()
  "Set up Dvips process for conversions via gs."
  (unless (preview-supports-image-type preview-gs-image-type)
    (error "preview-image-type setting '%s unsupported by this Emacs"
           preview-gs-image-type))
  (setq preview-gs-command-line (append
                                 preview-gs-command-line
                                 (list (preview-gs-resolution
                                        (preview-hook-enquiry preview-scale)
                                        (car preview-resolution)
                                        (cdr preview-resolution)))))
  (if preview-parsed-pdfoutput
      (preview-pdf2dsc-process-setup)
    (let ((process (preview-start-dvips preview-fast-conversion)))
      (setq TeX-sentinel-function #'preview-gs-dvips-sentinel)
      (list process (current-buffer) TeX-active-tempdir preview-ps-file
            preview-gs-image-type))))

(defun preview-dvipng-process-setup ()
  "Set up dvipng process for conversion."
  (setq preview-gs-command-line (append
                                 preview-gs-command-line
                                 (list (preview-gs-resolution
                                        (preview-hook-enquiry preview-scale)
                                        (car preview-resolution)
                                        (cdr preview-resolution)))))
  (if preview-parsed-pdfoutput
      (if (preview-supports-image-type preview-gs-image-type)
          (preview-pdf2dsc-process-setup)
        (error "preview-image-type setting '%s unsupported by this Emacs"
               preview-gs-image-type))
    (unless (preview-supports-image-type preview-dvipng-image-type)
      (error "preview-dvipng-image-type setting '%s unsupported by this Emacs"
             preview-dvipng-image-type))
    (let ((process (preview-start-dvipng)))
      (setq TeX-sentinel-function #'preview-dvipng-sentinel)
      (list process (current-buffer) TeX-active-tempdir t
          preview-dvipng-image-type))))


(defun preview-pdf2dsc-process-setup ()
  (let ((process (preview-start-pdf2dsc)))
    (setq TeX-sentinel-function #'preview-pdf2dsc-sentinel)
    (list process (current-buffer) TeX-active-tempdir preview-ps-file
          preview-gs-image-type)))

(defun preview-dvips-abort ()
  "Abort a Dvips run."
  (preview-gs-queue-empty)
  (condition-case nil
      (delete-file
       (let ((gsfile preview-gs-file))
         (with-current-buffer TeX-command-buffer
           (funcall (car gsfile) "dvi" t))))
    (file-error nil))
  (when preview-ps-file
    (condition-case nil
        (preview-delete-file preview-ps-file)
      (file-error nil)))
  (setq TeX-sentinel-function nil))

(defalias 'preview-dvipng-abort #'preview-dvips-abort)
;  "Abort a DviPNG run.")

(defun preview-gs-dvips-sentinel (process _command &optional gsstart)
  "Sentinel function for indirect rendering DviPS process.
The usual PROCESS and COMMAND arguments for
`TeX-sentinel-function' apply.  Starts gs if GSSTART is set."
  (condition-case err
      (let ((status (process-status process))
            (gsfile preview-gs-file))
        (cond ((eq status 'exit)
               (delete-process process)
               (setq TeX-sentinel-function nil)
               (condition-case nil
                   (delete-file
                    (with-current-buffer TeX-command-buffer
                      (funcall (car gsfile) "dvi" t)))
                 (file-error nil))
               (if preview-ps-file
                   (preview-prepare-fast-conversion))
               (when gsstart
                 (if preview-gs-queue
                     (preview-gs-restart)
                   (when preview-ps-file
                     (condition-case nil
                         (preview-delete-file preview-ps-file)
                       (file-error nil))))))
              ((eq status 'signal)
               (delete-process process)
               (preview-dvips-abort))))
    (error (preview-log-error err "DviPS sentinel" process)))
  (preview-reraise-error process))

(defun preview-pdf2dsc-sentinel (process _command &optional gsstart)
  "Sentinel function for indirect rendering PDF process.
The usual PROCESS and COMMAND arguments for
`TeX-sentinel-function' apply.  Starts gs if GSSTART is set."
  (condition-case err
      (let ((status (process-status process)))
        (cond ((eq status 'exit)
               (delete-process process)
               (setq TeX-sentinel-function nil)
               ;; Add DELAYBIND option for adjustment of foreground
               ;; color to work.
               (if (and (eq preview-pdf-color-adjust-method t)
                        (aref preview-colors 1))
                   (setq preview-gs-command-line (append
                                                  preview-gs-command-line
                                                  '("-dDELAYBIND"))))
               (setq preview-gs-init-string
                     (concat preview-gs-init-string
                             (preview-pdf-color-string preview-colors)))
               (preview-prepare-fast-conversion)
               (when gsstart
                 (if preview-gs-queue
                     (preview-gs-restart)
                   (when preview-ps-file
                     (condition-case nil
                         (preview-delete-file preview-ps-file)
                       (file-error nil))))))
              ((eq status 'signal)
               (delete-process process)
               (preview-dvips-abort))))
    (error (preview-log-error err "PDF2DSC sentinel" process)))
  (preview-reraise-error process))

(defun preview-gs-close (process closedata)
  "Clean up after PROCESS and set up queue accumulated in CLOSEDATA."
  (setq preview-gs-queue (nconc preview-gs-queue closedata))
  (if process
      (if preview-gs-queue
          (if TeX-process-asynchronous
              (if (and (eq (process-status process) 'exit)
                       (null TeX-sentinel-function))
                  ;; Process has already finished and run sentinel
                  (progn
                    (when preview-ps-file
                      (condition-case nil
                          (preview-delete-file preview-ps-file)
                        (file-error nil)))
                    (preview-gs-restart))
                (setq TeX-sentinel-function
                      (let ((fun (if preview-parsed-pdfoutput
                                     #'preview-pdf2dsc-sentinel
                                   #'preview-gs-dvips-sentinel)))
                        (lambda (process command)
                          (funcall fun process command t)))))
            (TeX-synchronous-sentinel "Preview-DviPS" (cdr preview-gs-file)
                                      process))
    ;; pathological case: no previews although we sure thought so.
        (delete-process process)
        (unless (eq (process-status process) 'signal)
          (preview-dvips-abort)))))

(defun preview-dvipng-sentinel (process _command &optional placeall)
  "Sentinel function for indirect rendering DviPNG process.
The usual PROCESS and COMMAND arguments for
`TeX-sentinel-function' apply.  Places all snippets if PLACEALL is set."
  (condition-case err
      (let ((status (process-status process)))
        (cond ((eq status 'exit)
               (delete-process process)
               (setq TeX-sentinel-function nil)
               (when placeall
                 (preview-dvipng-place-all)))
              ((eq status 'signal)
               (delete-process process)
               (preview-dvipng-abort))))
    (error (preview-log-error err "DviPNG sentinel" process)))
  (preview-reraise-error process))

(defun preview-dvipng-close (process closedata)
  "Clean up after PROCESS and set up queue accumulated in CLOSEDATA."
  (if preview-parsed-pdfoutput
      (preview-gs-close process closedata)
    (setq preview-gs-queue (nconc preview-gs-queue closedata))
    (if process
        (if preview-gs-queue
            (if TeX-process-asynchronous
                (if (and (eq (process-status process) 'exit)
                         (null TeX-sentinel-function))
                    ;; Process has already finished and run sentinel
                    (preview-dvipng-place-all)
                  (setq TeX-sentinel-function (lambda (process command)
                                                (preview-dvipng-sentinel
                                                 process
                                                 command
                                                 t))))
              (TeX-synchronous-sentinel "Preview-DviPNG" (cdr preview-gs-file)
                                        process))
          ;; pathological case: no previews although we sure thought so.
          (delete-process process)
          (unless (eq (process-status process) 'signal)
            (preview-dvipng-abort))))))

(defun preview-dsc-parse (file)
  "Parse DSC comments of FILE.
Returns a vector with offset/length pairs corresponding to
the pages.  Page 0 corresponds to the initialization section."
  (with-temp-buffer
    (set-buffer-multibyte nil)
    (insert-file-contents-literally file)
    (let ((last-pt (point-min))
          trailer
          pagelist
          lastbegin
          pt
          case-fold-search
          (level 0))
      (while (search-forward-regexp "\
%%\\(?:\\(BeginDocument:\\)\\|\
\\(EndDocument[\n\r]\\)\\|\
\\(Page:\\)\\|\
\\(Trailer[\n\r]\\)\\)" nil t)
        (setq pt (match-beginning 0))
        (cond ((null (memq (char-before pt) '(?\C-j ?\C-m nil))))
              (trailer (error "Premature %%%%Trailer in `%s' at offsets %d/%d"
                              file trailer pt))
              ((match-beginning 1)
               (if (zerop level)
                   (setq lastbegin pt))
               (setq level (1+ level)))
              ((match-beginning 2)
               (if (zerop level)
                   (error "Unmatched %%%%EndDocument in `%s' at offset %d"
                          file pt)
                 (setq level (1- level))))
              ((> level 0))
              ((match-beginning 3)
               (push (list last-pt (- pt last-pt)) pagelist)
               (setq last-pt pt))
              ((match-beginning 4)
               (setq trailer pt))))
      (unless (zerop level)
        (error "Unmatched %%%%BeginDocument in `%s' at offset %d"
               file lastbegin))
      (push (list last-pt
                  (- (or trailer (point-max)) last-pt)) pagelist)
      (vconcat (nreverse pagelist)))))

(defun preview-gs-dsc-cvx (page dsc)
  "Generate PostScript code accessing PAGE in the DSC object.
The returned PostScript code will need the file on
top of the stack, and will replace it with an executable
object corresponding to the wanted page."
  (let ((curpage (aref dsc page)))
    (format "dup %d setfileposition %d()/SubFileDecode filter cvx"
            (1- (car curpage)) (nth 1 curpage))))

(defun preview-ps-quote-filename (str &optional nonrel)
  "Make a PostScript string from filename STR.
The file name is first made relative unless
NONREL is not NIL."
  (unless nonrel (setq str (file-relative-name str)))
  (let ((index 0))
    (while (setq index (string-match "[\\()]" str index))
      (setq str (replace-match "\\\\\\&" t nil str)
            index (+ 2 index)))
    (concat "(" str ")")))

(defun preview-prepare-fast-conversion ()
  "This fixes up all parameters for fast conversion."
  (let* ((file (if (consp (car preview-ps-file))
                   (if (consp (caar preview-ps-file))
                       (car (last (caar preview-ps-file)))
                     (caar preview-ps-file))
                 (car preview-ps-file)))
         (all-files (if (and (consp (car preview-ps-file))
                             (consp (caar preview-ps-file)))
                        (caar preview-ps-file)
                      (list file))))
    (setq preview-gs-dsc (preview-dsc-parse file))
    (setq preview-gs-init-string
          ;; Add commands for revised file access controls introduced
          ;; after gs 9.27 (bug#37719)
          (concat (format "systemdict /.addcontrolpath known {%s} if "
                          (mapconcat (lambda (f)
                                       (format "/PermitFileReading %s .addcontrolpath"
                                               (preview-ps-quote-filename f)))
                                     all-files "\n"))
                  (format "{<</PermitFileReading[%s]>> setuserparams \
.locksafe} stopped pop "
                          (mapconcat #'preview-ps-quote-filename all-files ""))
                  preview-gs-init-string
                  (format " %s(r)file /.preview-ST 1 index def %s exec .preview-ST "
                          (preview-ps-quote-filename file)
                          (preview-gs-dsc-cvx 0 preview-gs-dsc))))))

(defun preview-gs-urgentize (ov buff)
  "Make a displayed overlay render with higher priority.
This function is used in fake conditional display properties
for reordering the conversion order to prioritize on-screen
images.  OV is the overlay in question, and BUFF is the
Ghostscript process buffer where the buffer-local queue
is located."
  ;; It does not matter that ov gets queued twice in that process: the
  ;; first version to get rendered will clear the 'queued property.
  ;; It cannot get queued more than twice since we remove the
  ;; conditional display property responsible for requeuing here.
  ;; We don't requeue if the overlay has been killed (its buffer made
  ;; nil).  Not necessary, but while we are checking...
  ;; We must return t.
  (preview-remove-urgentization ov)
  (when (and (overlay-get ov 'queued)
             (overlay-buffer ov))
    (with-current-buffer buff
      (push ov preview-gs-queue)))
  t)

(defsubst preview-icon-copy (icon)
  "Prepare a later call of `preview-replace-active-icon'."

  ;; This is just a GNU Emacs specific efficiency hack because it
  ;; is easy to do.  When porting, don't do anything complicated
  ;; here, rather deliver just the unchanged icon and make
  ;; `preview-replace-active-icon' do the necessary work of replacing
  ;; the icon where it actually has been stored, probably
  ;; in the car of the strings property of the overlay.  This string
  ;; might probably serve as a begin-glyph as well, in which case
  ;; modifying the string in the strings property would change that
  ;; glyph automatically.

  (cons 'image (cdr icon)))

(defsubst preview-replace-active-icon (ov replacement)
  "Replace the active Icon in OV by REPLACEMENT, another icon."
  (let ((img (overlay-get ov 'preview-image)))
    (setcdr (car img) (cdar replacement))
    (setcdr img (cdr replacement))))

(defun preview-gs-place (ov snippet box run-buffer tempdir ps-file _imagetype)
  "Generate an image placeholder rendered over by Ghostscript.
This enters OV into all proper queues in order to make it render
this image for real later, and returns the overlay after setting
a placeholder image.  SNIPPET gives the number of the
snippet in question for the file to be generated.
BOX is a bounding box if we already know one via TeX.
RUN-BUFFER is the buffer of the TeX process,
TEMPDIR is the correct copy of `TeX-active-tempdir',
PS-FILE is a copy of `preview-ps-file', IMAGETYPE is the image type
for the file extension."
  (overlay-put ov 'filenames
               (unless (eq ps-file t)
                 (list
                  (preview-make-filename
                   (or ps-file
                       (format "preview.%03d" snippet))
                   tempdir))))
  (overlay-put ov 'queued
               (vector box nil snippet))
  (overlay-put ov 'preview-image
               (list (preview-icon-copy preview-nonready-icon)))
  (preview-add-urgentization #'preview-gs-urgentize ov run-buffer)
  (list ov))

(defvar view-exit-action)

(eval-and-compile
  (defvar preview-button-1 [mouse-2])
  (defvar preview-button-2 [mouse-3]))

(defmacro preview-make-clickable (&optional map glyph helpstring click1 click2)
  "Generate a clickable string or keymap.
If MAP is non-nil, it specifies a keymap to add to, otherwise
a new one is created.  If GLYPH is given, the result is made
to display it wrapped in a string.  In that case,
HELPSTRING is a format string with one or two %s specifiers
for preview's clicks, displayed as a help-echo.  CLICK1 and CLICK2
are functions to call on preview's clicks."
  `(let ((resmap ,(or map '(make-sparse-keymap))))
     ,@(if click1
           `((define-key resmap preview-button-1 ,click1)))
     ,@(if click2
           `((define-key resmap preview-button-2 ,click2)))
     ,(if glyph
          `(propertize
            "x"
            'display ,glyph
            'mouse-face 'highlight
            'help-echo
            ,(if (stringp helpstring)
                 (format helpstring preview-button-1 preview-button-2)
               `(format ,helpstring preview-button-1 preview-button-2))
            'keymap resmap)
        'resmap)))

(defun preview-mouse-open-error (string)
  "Display STRING in a new view buffer on click."
  (let ((buff (get-buffer-create
               "*Preview-Ghostscript-Error*")))
    (with-current-buffer buff
      (kill-all-local-variables)
      (set (make-local-variable 'view-exit-action) #'kill-buffer)
      (setq buffer-undo-list t)
      (erase-buffer)
      (insert string)
      (goto-char (point-min)))
    (view-buffer-other-window buff)))

(defun preview-mouse-open-eps (file &optional position)
  "Display eps FILE in a view buffer on click.
Place point at POSITION, else beginning of file."
  (let ((default-mode
          ;; FIXME: Yuck!  Just arrange for the file name to have the right
          ;; extension instead!
          (assoc-default "x.ps" auto-mode-alist #'string-match))
        (buff (get-file-buffer file)))
    (save-excursion
      (if buff
          (pop-to-buffer buff)
        (view-file-other-window file))
      (if (and (eq major-mode (default-value 'major-mode))
               default-mode)
          (funcall default-mode))
      (goto-char (or position (point-min)))
      (message "%s" (substitute-command-keys "\
Try \\[ps-run-start] \\[ps-run-buffer] and \
\\<ps-run-mode-map>\\[ps-run-mouse-goto-error] on error offset.")))))

(defun preview-gs-flag-error (ov err)
  "Make an eps error flag in overlay OV for ERR string."
  (let* ((filenames (overlay-get ov 'filenames))
         (file (car (nth 0 filenames)))
         ;; FIXME: This format isn't equal to actual invocation of gs
         ;; command constructed in `preview-gs-restart', which
         ;; contains "%d".
         (outfile (format "-sOutputFile=%s"
                          (file-relative-name
                           (car (nth 1 filenames)))))
         (ps-open
          (let ((string
                 (concat
                (mapconcat #'shell-quote-argument
                            (append (list
                                     preview-gs-command
                                     outfile)
                                    preview-gs-command-line)
                            " ")
                 "\nGS>"
                 preview-gs-init-string
                 (aref (overlay-get ov 'queued) 1)
                 err)))
            (lambda () (interactive "@") (preview-mouse-open-error string))))
         (str
          (preview-make-clickable
           nil
           preview-error-icon
           "%s views error message
%s more options"
           ps-open
           (let ((args
                  (if preview-ps-file
                      (list
                       (if (consp (car file)) (nth 1 (car file)) (car file))
                       (nth 0 (aref preview-gs-dsc
                                    (aref (overlay-get ov 'queued) 2))))
                    (list file))))
             (lambda () (interactive)
               (popup-menu
                `("PostScript error"
                  ["View error" ,ps-open]
                  ["View source" ,(lambda () (interactive "@")
                                    (apply #'preview-mouse-open-eps
                                           args))])))))))
    (overlay-put ov 'strings (cons str str))
    (preview-toggle ov)))

(defun preview-gs-transact (process answer)
  "Work off Ghostscript transaction.
This routine is the action routine called via the process filter.
The Ghostscript process buffer of PROCESS will already be selected, and
and the standard output of Ghostscript up to the next prompt will be
given as ANSWER."
  (let ((ov (pop preview-gs-outstanding))
        (have-error (not
                     (string-match "\\`GS\\(<[0-9]+\\)?>\\'" answer ))))
    (when (and ov (overlay-buffer ov))
      (let ((queued (overlay-get ov 'queued)))
        (when queued
          (let* ((bbox (aref queued 0))
                 (filenames (overlay-get ov 'filenames))
                 (oldfile (nth 0 filenames))
                 (newfile (nth 1 filenames)))
            (if have-error
                (preview-gs-flag-error ov answer)
              (condition-case nil
                  (preview-delete-file oldfile)
                (file-error nil))
              (overlay-put ov 'filenames (cdr filenames))
              (preview-replace-active-icon
               ov
               (preview-create-icon (car newfile)
                                    preview-gs-image-type
                                    (preview-ascent-from-bb
                                     bbox)
                                    (aref preview-colors 2))))
            (overlay-put ov 'queued nil)))))
    (while (and (< (length preview-gs-outstanding)
                   preview-gs-outstanding-limit)
                (setq ov (pop preview-gs-queue)))
      (let ((queued (overlay-get ov 'queued)))
        (when (and queued
                   (not (memq ov preview-gs-outstanding))
                   (overlay-buffer ov))
          (let* ((filenames (overlay-get ov 'filenames))
                 (oldfile (car (nth 0
                                    (nconc filenames
                                           (list
                                            (preview-make-filename
                                             (format "pr%d-%d.%s"
                                                     (car preview-gs-sequence)
                                                     (cdr preview-gs-sequence)
                                                     preview-gs-image-type)
                                             TeX-active-tempdir))))))
                 (bbox (aset queued 0
                             (or (and preview-prefer-TeX-bb
                                      (aref queued 0))
                                 (and (stringp oldfile)
                                      (preview-extract-bb
                                       oldfile))
                                 (aref queued 0)
                                 (error "No bounding box"))))
                 (snippet (aref queued 2))
                 (gs-line
                  (format
                   "%s<<%s>>preview-do\n"
                   (if preview-ps-file
                       (concat "dup "
                               (preview-gs-dsc-cvx
                                snippet
                                preview-gs-dsc))
                     (format "%s(r)file cvx"
                             (preview-ps-quote-filename
                              (if (listp oldfile)
                                  (car (last oldfile))
                                oldfile))))
                   (if preview-parsed-tightpage
                       ""
                     (format "/PageSize[%g %g]/PageOffset[%g \
%g[1 1 dtransform exch]{0 ge{neg}if exch}forall]"
                             (- (aref bbox 2) (aref bbox 0))
                             (- (aref bbox 3) (aref bbox 1))
                             (aref bbox 0) (aref bbox 1))))))
            (setcdr preview-gs-sequence (1+ (cdr preview-gs-sequence)))
            (setq preview-gs-outstanding
                  (nconc preview-gs-outstanding
                         (list ov)))
            (aset queued 1 gs-line)
            ;; ignore errors because of dying processes: they will get
            ;; caught by the sentinel, anyway.
            (condition-case nil
                (process-send-string
                 process
                 gs-line)
              (error nil))))))
    (unless preview-gs-outstanding
      (condition-case nil
          (process-send-eof process)
        (error nil)))))

(defun preview-hook-enquiry (hook)
  "Gets a value from a configured hook.
HOOK is a list or single item, for which the first resolving to
non-nil counts.  Entries can be a callable function, or
a symbol that is consulted, or a value.  Lists are evaluated
recursively."
  (cond ((functionp hook)
         (funcall hook))
        ((consp hook)
         (let (res)
           (while (and (not res) hook)
             (setq res (preview-hook-enquiry (car hook))
                   hook (cdr hook)))
           res))
        ((and (symbolp hook) (boundp hook))
         (symbol-value hook))
        (t hook)))

(defun preview-inherited-face-attribute (face attribute &optional inherit)
  "Fetch face attribute while adhering to inheritance.
This searches FACE for an ATTRIBUTE, using INHERIT
for resolving unspecified or relative specs.  See the fourth
argument of function `face-attribute' for details."
  (face-attribute face attribute nil inherit))

(defcustom preview-scale-function #'preview-scale-from-face
  "Scale factor for included previews.
This can be either a function to calculate the scale, or
a fixed number."
  :group 'preview-appearance
  :type '(choice (function-item preview-scale-from-face)
                 (const 1.0)
                 (number :value 1.0)
                 (function :value preview-scale-from-face)))

(defcustom preview-default-document-pt 10
  "Assumed document point size for `preview-scale-from-face'.
If the point size (such as 11pt) of the document cannot be
determined from the document options itself, assume this size.
This is for matching screen font size and previews."
  :group 'preview-appearance
  :type
  '(choice (const :tag "10pt" 10)
           (const :tag "11pt" 11)
           (const :tag "12pt" 12)
           (number :tag "Other" :value 11.0)))

(defcustom preview-document-pt-list '(preview-parsed-font-size
                                      preview-auctex-font-size
                                      preview-default-document-pt)
  "How `preview-document-pt' figures out the document size."
  :group 'preview-appearance
  :type
  '(repeat (choice
            ;; FIXME: It seems that the bug mentioned below doesn't exist
            ;;        at least for emacs 27.2.
            ;; This is a bug: type function seems to match variables, too.
            (restricted-sexp :match-alternatives (functionp)
                             :tag "Function" :value preview-auctex-font-size)
            (variable :value preview-parsed-font-size)
            (number :value 11))))

(defun preview-auctex-font-size ()
  "Calculate the default font size of document.
If packages, classes or styles were called with an option
like 10pt, size is taken from the first such option if you
had let your document be parsed by AucTeX."
  (let* ((regexp "\\`\\([0-9]+\\)pt\\'")
         (option
          (or
           (LaTeX-match-class-option regexp)
           ;; We don't have `LaTeX-match-package-option'.
           (TeX-member regexp
                       (apply #'append
                              (mapcar #'cdr LaTeX-provided-package-options))
                       #'string-match))))
    (if option (string-to-number (match-string 1 option)))))

(defsubst preview-document-pt ()
  "Calculate the default font size of document."
  (preview-hook-enquiry preview-document-pt-list))

(defun preview-scale-from-face ()
  "Calculate preview scale from `preview-reference-face'.
This calculates the scale of EPS images from a document assumed
to have a default font size given by function `preview-document-pt'
so that they match the reference face in height."
  (let ((d (/ (preview-inherited-face-attribute 'preview-reference-face :height
                                                'default)
              10.0)))
    (lambda () (/ d (preview-document-pt)))))

(defvar preview-min-spec nil
  "Value to filter out too large icons.
Icon specs with :size larger than this value is not used.
Appropriate value is determined at run time according to the
display in use.")

(defun preview-make-image (symbol)
  "Make an image from a preview spec list.
The first spec that is workable (given the current setting of
`preview-min-spec') from the given SYMBOL is used here.  The
icon is cached in the property list of the SYMBOL."
  (let ((alist (get symbol 'preview-min-alist)))
    (cdr (or
          (assq preview-min-spec alist)
          (car (put symbol 'preview-min-alist
                    (cons
                     (cons preview-min-spec
                           (preview-filter-specs
                            (symbol-value symbol)))
                     alist)))))))

(defun preview-filter-specs (spec-list)
  "Find the first of the fitting specs and make an image."
  (let (image)
    (while (and spec-list
                (not (setq image
                           (catch 'preview-filter-specs
                             (preview-filter-specs-1 (car spec-list))))))
      (setq spec-list (cdr spec-list)))
    image))

(defun preview-filter-specs-1 (specs)
  (and specs
       (if (get 'preview-filter-specs (car specs))
           (apply (get 'preview-filter-specs (car specs)) specs)
         `(,(nth 0 specs) ,(nth 1 specs)
           ,@(preview-filter-specs-1 (nthcdr 2 specs))))))

(put 'preview-filter-specs :min
     #'(lambda (_keyword value &rest args)
         (if (> value preview-min-spec)
             (throw 'preview-filter-specs nil)
           (preview-filter-specs-1 args))))

(put 'preview-filter-specs :file
     #'(lambda (_keyword value &rest args)
         `(:file ,(expand-file-name value (expand-file-name "images"
                                                            TeX-data-directory))
                 ,@(preview-filter-specs-1 args))))

(defun preview-ascent-from-bb (bb)
  "This calculates the image ascent from its bounding box.
The bounding box BB needs to be a 4-component vector of
numbers (can be float if available)."
  ;; baseline is at 1in from the top of letter paper (11in), so it is
  ;; at 10in from the bottom precisely, which is 720 in PostScript
  ;; coordinates.  If our bounding box has its bottom not above this
  ;; line, and its top above, we can calculate a useful ascent value.
  ;; If not, something is amiss.  We just use 100 in that case.

  (let ((bottom (aref bb 1))
        (top (aref bb 3)))
    (if (and (<= bottom 720)
             (> top 720))
        (round (* 100.0 (/ (- top 720.0) (- top bottom))))
      100)))

(defface preview-face '((((background dark))
                         (:background "dark slate gray"))
                        (t
                         (:background "beige")))
  "Face to use for the preview source."
  :group 'preview-appearance)

(defface preview-reference-face '((t nil))
  "Face consulted for colors and scale of active previews.
Fallback to :inherit and 'default implemented."
  :group 'preview-appearance)

(defcustom preview-auto-reveal
  '(eval (preview-arrived-via (key-binding [left]) (key-binding [right])
                              #'backward-char #'forward-char))
  "Cause previews to open automatically when entered.
Possibilities are:
T autoopens,
NIL doesn't,
a symbol will have its value consulted if it exists,
defaulting to NIL if it doesn't.
An integer will specify a maximum cursor movement distance.
Larger movements won't open the preview.
A CONS-cell means to call a function for determining the value.
The CAR of the cell is the function to call which receives
the CDR of the CONS-cell in the rest of the arguments, while
point and current buffer point to the position in question.
All of the options show reasonable defaults."
  :group 'preview-appearance
  :type '(choice (const :tag "Off" nil)
                 (const :tag "On" t)
                 (symbol :tag "Indirect variable" :value reveal-mode)
                 (integer :tag "Maximum distance" :value 1)
                 (cons :tag "Function call"
                       :value (eval (preview-arrived-via
                                     (key-binding [left])
                                     (key-binding [right])))
                       function (list :tag "Argument list"
                                      (repeat :inline t sexp)))))

(defun preview-auto-reveal-p (mode distance)
  "Decide whether to auto-reveal.
Returns non-NIL if region should be auto-opened.
See `preview-auto-reveal' for definitions of MODE, which gets
set to `preview-auto-reveal'.  DISTANCE specifies the movement
distance with which point has been reached in case it has been
a movement starting in the current buffer."
  (cond ((symbolp mode)
         (and (boundp mode)
              (symbol-value mode)))
        ((integerp mode)
         (and distance (/= 0 distance) (<= (abs distance) mode)))
        ((consp mode)
         (apply (car mode) (cdr mode)))
        (t mode)))

(defun preview-arrived-via (&rest list)
  "Indicate auto-opening.
Returns non-NIL if called by one of the commands in LIST."
  (memq this-command list))

(defcustom preview-equality-transforms '(identity
                                         preview-canonical-spaces)
"Transformation functions for region changes.
These functions are tried in turn on the strings from the
regions of a preview to decide whether a preview is to be considered
changed.  If any transform leads to equal results, the preview is
considered unchanged."
  :group 'preview-appearance
  :type '(repeat function))

(defcustom preview-transparent-color '(highlight :background)
  "Color to appear transparent in previews.
Set this to something unusual when using `preview-transparent-border',
to the default background in most other cases."
  :type '(radio (const :tag "None" nil)
                 (const :tag "Autodetect" t)
                 (color :tag "By name" :value "white")
                 (list :tag "Take from face"
                       :value (default :background)
                       (face)
                       (choice :tag "What to take"
                        (const :tag "Background" :value :background)
                        (const :tag "Foreground" :value :foreground))))
  :group 'preview-appearance)

;; Note that the following default introduces a border only when
;; Emacs blinks politely when point is on an image (the tested
;; unrelated function was introduced at about the time image blinking
;; became tolerable).
(defcustom preview-transparent-border nil
  "Width of transparent border for previews in pt.
Setting this to a numeric value will add a border of
`preview-transparent-color' around images, and will turn
the heuristic-mask setting of images to default to 't since
then the borders are correctly detected even in case of
palette operations.  If the transparent color is something
not present otherwise in the image, the cursor display
will affect just this border.  A width of 0 is interpreted
by PostScript as meaning a single pixel, other widths are
interpreted as PostScript points (1/72 of 1in)"
  :group 'preview-appearance
  :type '(choice (const :value nil :tag "No border")
                 (number :value 1.5 :tag "Border width in pt")))

(defun preview-get-heuristic-mask ()
  "Get heuristic-mask to use for previews.
Consults `preview-transparent-color'."
  (cond ((stringp preview-transparent-color)
         (color-values preview-transparent-color))
        ((or (not (consp preview-transparent-color))
             (integerp (car preview-transparent-color)))
         preview-transparent-color)
        (t (color-values (preview-inherited-face-attribute
                          (nth 0 preview-transparent-color)
                          (nth 1 preview-transparent-color)
                          'default)))))

(defsubst preview-create-icon-1 (file type ascent border)
  `(image
    :file ,file
    :type ,type
    :ascent ,ascent
    ,@(and border
           '(:mask (heuristic t)))))

(defun preview-create-icon (file type ascent border)
  "Create an icon from FILE, image TYPE, ASCENT and BORDER."
  (list
   (preview-create-icon-1 file type ascent border)
   file type ascent border))

(put 'preview-filter-specs :type
     (lambda (_keyword value &rest args)
       (if (image-type-available-p value)
           `(image :type ,value
                   ,@(preview-filter-specs-1 args))
         (throw 'preview-filter-specs nil))))

(defun preview-import-image (image)
  "Convert the printable IMAGE rendition back to an image."
  (cond ((stringp image)
         (propertize image 'face 'preview-face))
        ((eq (car image) 'image)
         image)
        (t
         (preview-create-icon-1 (nth 0 image)
                                (nth 1 image)
                                (nth 2 image)
                                (if (< (length image) 4)
                                    (preview-get-heuristic-mask)
                                  (nth 3 image))))))

;; No defcustom here: does not seem to make sense.

(defvar preview-tb-icon-specs
  '((:type xpm :file "prvtex24.xpm")
    (:type xbm :file "prvtex24.xbm")))

(defvar preview-tb-icon nil)

(defun preview-add-urgentization (fun ov &rest rest)
  "Cause FUN (function call form) to be called when redisplayed.
FUN must be a form with OV as first argument,
REST as the remainder, returning T."
  (let ((dispro (overlay-get ov 'display)))
    (unless (eq (car dispro) 'when)
      (overlay-put ov 'display `(when (,fun ,ov ,@rest)  . ,dispro)))))

(defun preview-remove-urgentization (ov)
  "Undo urgentization of OV by `preview-add-urgentization'.
Returns the old arguments to `preview-add-urgentization'
if there was any urgentization."
  (let ((dispro (overlay-get ov 'display)))
    (when (eq (car-safe dispro) 'when)
      (prog1
          (car (cdr dispro))
        (overlay-put ov 'display (cdr (cdr dispro)))))))

(defvar preview-overlay nil)

(put 'preview-overlay
     'modification-hooks
     '(preview-handle-modification))

(put 'preview-overlay
     'insert-in-front-hooks
     '(preview-handle-insert-in-front))

(put 'preview-overlay
     'insert-behind-hooks
     '(preview-handle-insert-behind))

;; We have to fake our way around atomicity.

;; Here is the beef: for best intuitiveness, we want to have
;; insertions be carried out as expected before iconized text
;; passages, but we want to insert *into* the overlay when not
;; iconized.  A preview that has become empty can not get content
;; again: we remove it.  A disabled preview needs no insert-in-front
;; handler.

(defvar preview-change-list nil
  "List of tentatively changed overlays.")

(defcustom preview-dump-threshold
  "^ *\\\\begin *{document}[ %]*$"
  "Regexp denoting end of preamble.
This is the location up to which preamble changes are considered
to require redumping of a format."
  :group 'preview-latex
  :type 'string)

(defun preview-preamble-changed-function
  (ov _after-change _beg _end &optional _length)
  "Hook function for change hooks on preamble.
See info node `(elisp) Overlay Properties' for
definition of OV, AFTER-CHANGE, BEG, END and LENGTH."
  (let ((format-cons (overlay-get ov 'format-cons)))
    (preview-unwatch-preamble format-cons)
    (preview-format-kill format-cons)
    (setcdr format-cons t)))

(defun preview-watch-preamble (file command format-cons)
  "Set up a watch on master file FILE.
FILE can be an associated buffer instead of a filename.
COMMAND is the command that generated the format.
FORMAT-CONS contains the format info for the main
format dump handler."
  (let ((buffer (if (bufferp file)
                    file
                  (find-buffer-visiting file))) ov)
    (setcdr
     format-cons
     (cons command
           (when buffer
             (with-current-buffer buffer
               (save-excursion
                 (save-restriction
                   (widen)
                   (goto-char (point-min))
                   (unless (re-search-forward preview-dump-threshold nil t)
                     (error "Can't find preamble of `%s'" file))
                   (setq ov (make-overlay (point-min) (point)))
                   (overlay-put ov 'format-cons format-cons)
                   (overlay-put ov 'insert-in-front-hooks
                                '(preview-preamble-changed-function))
                   (overlay-put ov 'modification-hooks
                                '(preview-preamble-changed-function))
                   ov))))))))

(defun preview-unwatch-preamble (format-cons)
  "Stop watching a format on FORMAT-CONS.
The watch has been set up by `preview-watch-preamble'."
  (when (consp (cdr format-cons))
    (when (cddr format-cons)
      (delete-overlay (cddr format-cons)))
    (setcdr (cdr format-cons) nil)))

(defun preview-register-change (ov)
  "Register not yet changed OV for verification.
This stores the old contents of the overlay in the
`preview-prechange' property and puts the overlay into
`preview-change-list' where `preview-check-changes' will
find it at some later point of time."
  (unless (overlay-get ov 'preview-prechange)
    (if (eq (overlay-get ov 'preview-state) 'disabled)
        (overlay-put ov 'preview-prechange t)
      (overlay-put ov 'preview-prechange
                   (save-restriction
                     (widen)
                     (buffer-substring-no-properties
                      (overlay-start ov) (overlay-end ov)))))
    (push ov preview-change-list)))

(defun preview-check-changes ()
  "Check whether the contents under the overlay have changed.
Disable it if that is the case.  Ignores text properties."
  (dolist (ov preview-change-list)
    (condition-case nil
        (with-current-buffer (overlay-buffer ov)
          (let ((text (save-restriction
                        (widen)
                        (buffer-substring-no-properties
                         (overlay-start ov) (overlay-end ov)))))
            (if (zerop (length text))
                (preview-delete ov)
              (unless
                  (or (eq (overlay-get ov 'preview-state) 'disabled)
                      (preview-relaxed-string=
                       text (overlay-get ov 'preview-prechange)))
                (overlay-put ov 'insert-in-front-hooks nil)
                (overlay-put ov 'insert-behind-hooks nil)
                (preview-disable ov)))))
      (error nil))
    (overlay-put ov 'preview-prechange nil))
  (setq preview-change-list nil))

(defun preview-handle-insert-in-front
  (ov after-change _beg end &optional _length)
  "Hook function for `insert-in-front-hooks' property.
See info node `(elisp) Overlay Properties' for
definition of OV, AFTER-CHANGE, BEG, END and LENGTH."
  (if after-change
      (unless undo-in-progress
        (if (eq (overlay-get ov 'preview-state) 'active)
            (move-overlay ov end (overlay-end ov))))
    (preview-register-change ov)))

(defun preview-handle-insert-behind
  (ov after-change beg _end &optional _length)
  "Hook function for `insert-behind-hooks' property.
This is needed in case `insert-before-markers' is used at the
end of the overlay.  See info node `(elisp) Overlay Properties'
for definition of OV, AFTER-CHANGE, BEG, END and LENGTH."
  (if after-change
      (unless undo-in-progress
        (if (eq (overlay-get ov 'preview-state) 'active)
            (move-overlay ov (overlay-start ov) beg)))
    (preview-register-change ov)))

(defun preview-handle-modification
  (ov after-change _beg _end &optional _length)
  "Hook function for `modification-hooks' property.
See info node `(elisp) Overlay Properties' for
definition of OV, AFTER-CHANGE, BEG, END and LENGTH."
  (unless after-change
    (preview-register-change ov)))

(defun preview-toggle (ov &optional arg event)
  "Toggle visibility of preview overlay OV.
ARG can be one of the following: t displays the overlay,
nil displays the underlying text, and 'toggle toggles.
If EVENT is given, it indicates the window where the event
occured, either by being a mouse event or by directly being
the window in question.  This may be used for cursor restoration
purposes."
  (let ((old-urgent (preview-remove-urgentization ov))
        (preview-state
         (if (if (eq arg 'toggle)
                 (null (eq (overlay-get ov 'preview-state) 'active))
               arg)
             'active
           'inactive))
        (strings (overlay-get ov 'strings)))
    (unless (eq (overlay-get ov 'preview-state) 'disabled)
      (overlay-put ov 'preview-state preview-state)
      (if (eq preview-state 'active)
          (progn
            (overlay-put ov 'category 'preview-overlay)
            (if (eq (overlay-start ov) (overlay-end ov))
                (overlay-put ov 'before-string (car strings))
              (dolist (prop '(display keymap mouse-face help-echo))
                (overlay-put ov prop
                             (get-text-property 0 prop (car strings))))
              (overlay-put ov 'before-string nil))
            (overlay-put ov 'face nil))
        (dolist (prop '(display keymap mouse-face help-echo))
          (overlay-put ov prop nil))
        (overlay-put ov 'face 'preview-face)
        (unless (cdr strings)
          (setcdr strings (preview-inactive-string ov)))
        (overlay-put ov 'before-string (cdr strings)))
      (if old-urgent
          (apply #'preview-add-urgentization old-urgent))))
  (if event
      (preview-restore-position
       ov
       (if (windowp event)
           event
         (posn-window (event-start event))))))

(defvar preview-marker (make-marker)
  "Marker for fake intangibility.")

(defvar preview-temporary-opened nil)

(defvar preview-last-location nil
  "Restored cursor position marker for reopened previews.")
(make-variable-buffer-local 'preview-last-location)

(defun preview-mark-point ()
  "Mark position for fake intangibility."
  (when (eq (get-char-property (point) 'preview-state) 'active)
    (unless preview-last-location
      (setq preview-last-location (make-marker)))
    (set-marker preview-last-location (point))
    (set-marker preview-marker (point))
    (preview-move-point))
  (set-marker preview-marker (point)))

(defun preview-restore-position (ov window)
  "Tweak position after opening/closing preview.
The treated overlay OV has been triggered in WINDOW.  This function
records the original buffer position for reopening, or restores it
after reopening.  Note that by using the mouse, you can open/close
overlays not in the active window."
  (when (eq (overlay-buffer ov) (window-buffer window))
    (with-current-buffer (overlay-buffer ov)
      (if (eq (overlay-get ov 'preview-state) 'active)
          (setq preview-last-location
                (set-marker (or preview-last-location (make-marker))
                            (window-point window)))
        (when (and
               (markerp preview-last-location)
               (eq (overlay-buffer ov) (marker-buffer preview-last-location))
               (< (overlay-start ov) preview-last-location)
               (> (overlay-end ov) preview-last-location))
          (set-window-point window preview-last-location))))))

(defun preview-move-point ()
  "Move point out of fake-intangible areas."
  (preview-check-changes)
  (let* (newlist (pt (point)) (lst (overlays-at pt)) distance)
    (setq preview-temporary-opened
          (dolist (ov preview-temporary-opened newlist)
            (and (overlay-buffer ov)
                 (eq (overlay-get ov 'preview-state) 'inactive)
                 (if (and (eq (overlay-buffer ov) (current-buffer))
                          (or (<= pt (overlay-start ov))
                              (>= pt (overlay-end ov))))
                     (preview-toggle ov t)
                   (push ov newlist)))))
    (when lst
      (if (or disable-point-adjustment
              global-disable-point-adjustment
              (preview-auto-reveal-p
               preview-auto-reveal
               (setq distance
                     (and (eq (marker-buffer preview-marker)
                              (current-buffer))
                          (- pt (marker-position preview-marker))))))
          (preview-open-overlays lst)
        (while lst
          (setq lst
                (if (and
                     (eq (overlay-get (car lst) 'preview-state) 'active)
                     (> pt (overlay-start (car lst))))
                    (overlays-at
                     (setq pt (if (and distance (< distance 0))
                                  (overlay-start (car lst))
                                (overlay-end (car lst)))))
                  (cdr lst))))
        (goto-char pt)))))

(defun preview-open-overlays (list &optional pos)
  "Open all previews in LIST, optionally restricted to enclosing POS."
  (dolist (ovr list)
    (when (and (eq (overlay-get ovr 'preview-state) 'active)
               (or (null pos)
                   (and
                    (> pos (overlay-start ovr))
                    (< pos (overlay-end ovr)))))
      (preview-toggle ovr)
      (push ovr preview-temporary-opened))))

(if (fboundp 'advice-add)               ;Emacs≥24.4 (or ELPA package nadvice)
    nil ; See the defcustom below.
  (defadvice replace-highlight (before preview)
    (preview--open-for-replace (ad-get-arg 0) (ad-get-arg 1))))

(defun preview--open-for-replace (beg end &rest _)
  "Make `query-replace' open preview text about to be replaced."
  (preview-open-overlays (overlays-in beg end)))

(defcustom preview-query-replace-reveal t
  "Make `query-replace' autoreveal previews."
  :group 'preview-appearance
  :type 'boolean
  :require 'preview
  :set (lambda (symbol value)
         (set-default symbol value)
         (if (fboundp 'advice-add) ; COMPATIBILITY for Emacs<24.4
             (if value
                 (advice-add 'replace-highlight :before
                             #'preview--open-for-replace)
               (advice-remove 'replace-highlight
                              #'preview--open-for-replace))
           (if value
               (ad-enable-advice 'replace-highlight 'before 'preview)
             (ad-disable-advice 'replace-highlight 'before 'preview))
           (ad-activate 'replace-highlight)))
  :initialize #'custom-initialize-reset)

(defun preview-relaxed-string= (&rest args)
"Check for functional equality of arguments.
The arguments ARGS are checked for equality by using
`preview-equality-transforms' on them until it is exhausted
or one transform returns equality."
  (let ((lst preview-equality-transforms))
    (while (and lst (not (apply #'string= (mapcar (car lst) args))))
      (setq lst (cdr lst)))
    lst))

(defun preview-canonical-spaces (arg)
  "Convert ARG into canonical form.
Removes comments and collapses white space, except for multiple newlines."
  (let (pos)
    (while (setq pos (string-match "\\s<.*[\n\r][ \t]*" arg pos))
      (setq arg (replace-match "" t t arg 0)))
    (while (setq pos (string-match "[ \t]*\\(\\([ \t]\\)\\|[\n\r][ \t]*\\)"
                                   arg pos))
      (setq arg (replace-match (if (match-beginning 2) " " "\n") t t arg 0)
            pos (1+ pos)))
    (while (setq pos (string-match "\n+" arg pos))
      (if (string= "\n" (match-string 0 arg))
          (setq arg (replace-match " " t t arg 0)
                pos (1+ pos))
        (setq pos (match-end 0)))))
  arg)

(defun preview-regenerate (ovr)
  "Pass the modified region in OVR again through LaTeX."
  (let ((begin (overlay-start ovr))
        (end (overlay-end ovr)))
    (with-current-buffer (overlay-buffer ovr)
      (preview-delete ovr)
      (preview-region begin end))))

(defcustom preview-inner-environments '("Bmatrix" "Vmatrix" "aligned"
                                        "array" "bmatrix" "cases"
                                        "gathered" "matrix" "pmatrix"
                                        "smallmatrix" "split"
                                        "subarray" "vmatrix")
  "Environments not to be previewed on their own."
  :group 'preview-latex
  :type '(repeat string))


(defun preview-next-border (backwards)
  "Search for the next interesting border for `preview-at-point'.
Searches backwards if BACKWARDS is non-nil."
  (let (history preview-state (pt (point)))
    (catch 'exit
      (while
          (null
           (memq
            (setq preview-state
                  (if backwards
                      (if (> (setq pt
                                   (previous-single-char-property-change
                                    pt 'preview-state)) (point-min))
                          (get-char-property (1- pt) 'preview-state)
                        (throw 'exit (or history (point-min))))
                    (if (< (setq pt
                                 (next-single-char-property-change
                                  pt 'preview-state)) (point-max))
                        (get-char-property pt 'preview-state)
                      (throw 'exit (or history (point-max))))))
            '(active inactive)))
        (setq history (and (not preview-state) pt)))
      (or history pt))))

(defun preview-at-point ()
  "Do the appropriate preview thing at point.
If point is positioned on or inside of an unmodified preview area,
its visibility is toggled.

If not, the surroundings are run through preview.  The
surroundings don't extend into unmodified previews or past
contiguous previews invalidated by modifications.

Overriding any other action, if a region is
active (`transient-mark-mode'), it is run through `preview-region'."
  (interactive)
  (if (TeX-active-mark)
      (preview-region (region-beginning) (region-end))
    (catch 'exit
      (dolist (ovr (overlays-in (max (point-min) (1- (point)))
                                (min (point-max) (1+ (point)))))
        (let ((preview-state (overlay-get ovr 'preview-state)))
          (when preview-state
            (unless (eq preview-state 'disabled)
              (preview-toggle ovr 'toggle (selected-window))
              (throw 'exit t)))))
      (preview-region (preview-next-border t)
                      (preview-next-border nil)))))

(defun preview-disabled-string (ov)
  "Generate a before-string for disabled preview overlay OV."
  (concat (preview-make-clickable
           (overlay-get ov 'preview-map)
           preview-icon
           "\
%s regenerates preview
%s more options"
           (lambda () (interactive) (preview-regenerate ov)))
;; icon on separate line only for stuff starting on its own line
          (with-current-buffer (overlay-buffer ov)
            (save-excursion
              (save-restriction
                (widen)
                (goto-char (overlay-start ov))
                (if (bolp) "\n" ""))))))

(defun preview-disable (ovr)
  "Change overlay behaviour of OVR after source edits."
  (overlay-put ovr 'queued nil)
  (preview-remove-urgentization ovr)
  (overlay-put ovr 'preview-image nil)
  (overlay-put ovr 'timestamp nil)
  (setcdr (overlay-get ovr 'strings) (preview-disabled-string ovr))
  (preview-toggle ovr)
  (overlay-put ovr 'preview-state 'disabled)
  (dolist (filename (overlay-get ovr 'filenames))
    (condition-case nil
        (preview-delete-file filename)
      (file-error nil))
    (overlay-put ovr 'filenames nil)))

(defun preview-delete (ovr &rest _ignored)
  "Delete preview overlay OVR, taking any associated file along.
IGNORED arguments are ignored, making this function usable as
a hook in some cases"
  (let ((filenames (overlay-get ovr 'filenames)))
    (overlay-put ovr 'filenames nil)
    (delete-overlay ovr)
    (dolist (filename filenames)
      (condition-case nil
          (preview-delete-file filename)
        (file-error nil)))))

(defun preview-clearout (&optional start end timestamp)
  "Clear out all previews in the current region.
When called interactively, the current region is used.
Non-interactively, the region between START and END is
affected.  Those two values default to the borders of
the entire buffer.  If TIMESTAMP is non-nil, previews
with a `timestamp' property of it are kept."
  (interactive "r")
  (dolist (ov (overlays-in (or start (point-min))
                           (or end (point-max))))
    (and (overlay-get ov 'preview-state)
         (not (and timestamp
                   (equal timestamp (overlay-get ov 'timestamp))))
         (preview-delete ov))))

(defun preview-clearout-buffer (&optional buffer)
  "Clearout BUFFER from previews, current buffer if nil."
  (interactive)
  (if buffer
      (with-current-buffer buffer (preview-clearout))
    (preview-clearout)))

(defun preview-clearout-section ()
  "Clearout previews from LaTeX section."
  (interactive)
  (save-excursion
    (LaTeX-mark-section)
    (preview-clearout (region-beginning) (region-end))))

(defun preview-clearout-at-point ()
  "Clearout any preview at point."
  (interactive)
  (preview-clearout (max (point-min) (1- (point)))
                    (min (point-max) (1+ (point)))))

(defun preview-walk-document (func)
  "Cycle through all buffers belonging to current document.
Each buffer having the same master file as the current file
has FUNC called with its current buffer being set to it."
  (let* ((buffers (buffer-list))
         (master (expand-file-name (TeX-master-file t)))
         (default-buffers (list (current-buffer)
                                (find-buffer-visiting master))))
    (while buffers
      (with-current-buffer (pop buffers)
        (when
            (or (memq (current-buffer) default-buffers)
                (and (memq major-mode '(plain-tex-mode latex-mode))
                     (or (stringp TeX-master)
                         (eq TeX-master t))
                     (string= (expand-file-name (TeX-master-file t))
                              master)))
          (funcall func))))))

(defun preview-clearout-document ()
  "Clear out all previews in current document.
The document consists of all buffers that have the same master file
as the current buffer.  This makes the current document lose
all previews."
  (interactive)
  (preview-walk-document #'preview-clearout-buffer))

(defun preview-kill-buffer-cleanup (&optional buf)
  "This is a cleanup function just for use in hooks.
Cleans BUF or current buffer.  The difference to
`preview-clearout-buffer' is that previews
associated with the last buffer modification time are
kept."
  (with-current-buffer (or buf (current-buffer))
    (save-restriction
      (widen)
      (preview-clearout (point-min) (point-max) (visited-file-modtime)))))

(add-hook 'kill-buffer-hook #'preview-kill-buffer-cleanup)
(add-hook 'before-revert-hook #'preview-kill-buffer-cleanup)

(defvar preview-last-counter nil
  "Last counter information.")

(defun preview-extract-counters (ctr)
  (setq preview-last-counter
        (prog1 (copy-sequence ctr)
          (dolist (elt preview-last-counter)
            (setq ctr (delete elt ctr)))))
  (apply #'concat ctr))

(defun desktop-buffer-preview-misc-data (&rest _ignored)
  "Hook function that extracts previews for persistent sessions."
  (unless (buffer-modified-p)
    (setq preview-last-counter nil)
    (save-restriction
      (widen)
      (let (save-info (timestamp (visited-file-modtime)))
        (dolist (ov (sort (overlays-in (point-min) (point-max))
                          (lambda (x y) (< (overlay-start x)
                                           (overlay-start y)))))
          (when (and (memq (overlay-get ov 'preview-state) '(active inactive))
                     (null (overlay-get ov 'queued))
                     (cdr (overlay-get ov 'preview-image)))
            (push (preview-dissect ov timestamp) save-info)))
        (and save-info
             (cons 'preview (cons timestamp (nreverse save-info))))))))

(eval-after-load "desktop"
  '(add-hook
    'desktop-buffer-misc-functions
    #'desktop-buffer-preview-misc-data))

(defvar preview-temp-dirs nil
"List of top level temporary directories in use from preview.
Any directory not in this list will be cleared out by preview
on first use.")

(defun preview-dissect (ov timestamp)
  "Extract all persistent data from OV and TIMESTAMP it."
  (let ((filenames (butlast (nth 0 (overlay-get ov 'filenames)))))
    (overlay-put ov 'timestamp timestamp)
    (list (overlay-start ov)
          (overlay-end ov)
          (cdr (overlay-get ov 'preview-image))
          filenames
          (let ((ctr (overlay-get ov 'preview-counters)))
            (and ctr
                 (cons (preview-extract-counters (car ctr))
                       (preview-extract-counters (cdr ctr))))))))

(defun preview-buffer-restore-internal (buffer-misc)
  "Restore previews from BUFFER-MISC if proper.
Remove them if they have expired."
  (let ((timestamp (visited-file-modtime)) tempdirlist files)
    (setq preview-parsed-counters nil)
    (when (eq 'preview (pop buffer-misc))
      (preview-get-geometry)
      (if (equal (pop buffer-misc) timestamp)
          (dolist (ovdata buffer-misc)
            (setq tempdirlist
                  (apply #'preview-reinstate-preview tempdirlist
                         timestamp ovdata)))
        (dolist (ovdata buffer-misc)
          (setq files (nth 3 ovdata))
          (condition-case nil
              (delete-file (nth 0 files))
            (file-error nil))
          (unless (member (nth 1 files) tempdirlist)
            (push (nth 1 files) tempdirlist)))
        (dolist (dir tempdirlist)
          (condition-case nil
              (delete-directory dir)
            (file-error nil)))))))


(defun preview-buffer-restore (buffer-misc)
  "At end of desktop load, reinstate previews.
This delay is so that minor modes changing buffer positions
\(like `x-symbol-mode' does) will not wreak havoc.
BUFFER-MISC is the appropriate data to be used."
  (add-hook 'desktop-delay-hook
            (let ((buf (current-buffer)))
              (lambda ()
                (with-current-buffer buf
                  (preview-buffer-restore-internal
                   buffer-misc))))))

(defun desktop-buffer-preview (file-name _buffer-name misc)
  "Hook function for restoring persistent previews into a buffer."
  (when (and file-name (file-readable-p file-name))
    (let ((buf (find-file-noselect file-name)))
      (if (eq (car misc) 'preview)
          (with-current-buffer buf
            (preview-buffer-restore misc)
            buf)
        buf))))

(eval-after-load "desktop"
  '(if (boundp 'desktop-buffer-mode-handlers)
       (add-to-list 'desktop-buffer-mode-handlers
                    '(latex-mode . desktop-buffer-preview))
     (defvar desktop-buffer-file-name)
     (defvar desktop-buffer-name)
     (defvar desktop-buffer-misc)
     (add-hook 'desktop-buffer-handlers (lambda ()
                                          (desktop-buffer-preview
                                           desktop-buffer-file-name
                                           desktop-buffer-name
                                           desktop-buffer-misc)))))

(defcustom preview-auto-cache-preamble 'ask
  "Whether to generate a preamble cache format automatically.
Possible values are nil, t, and `ask'."
  :group 'preview-latex
  :type '(choice (const :tag "Cache" t)
                 (const :tag "Don't cache" nil)
                 (const :tag "Ask" ask)))

(defvar preview-dumped-alist nil
  "Alist of dumped masters.
The elements are (NAME . ASSOC).  NAME is the master file name
\(without extension), ASSOC is what to do with regard to this
format.  Possible values: NIL means no format is available
and none should be generated.  T means no format is available,
it should be generated on demand.  If the value is a cons cell,
the CAR of the cons cell is the command with which the format
has been generated, and the CDR is some Emacs-flavor specific
value used for maintaining a watch on possible changes of the
preamble.")

(defun preview-cleanout-tempfiles ()
  "Clean out all directories and files with non-persistent data.
This is called as a hook when exiting Emacs."
  (mapc #'preview-kill-buffer-cleanup (buffer-list))
  (mapc #'preview-format-kill preview-dumped-alist))

(defun preview-inactive-string (ov)
  "Generate before-string for an inactive preview overlay OV.
This is for overlays where the source text has been clicked
visible.  For efficiency reasons it is expected that the buffer
is already selected and unnarrowed."
  (concat
   (preview-make-clickable (overlay-get ov 'preview-map)
                           preview-icon
                           "\
%s redisplays preview
%s more options")
;; icon on separate line only for stuff starting on its own line
   (with-current-buffer (overlay-buffer ov)
     (save-excursion
       (save-restriction
         (widen)
         (goto-char (overlay-start ov))
         (if (bolp) "\n" ""))))))

(defun preview-dvipng-place-all ()
  "Place all images dvipng has created, if any.
Deletes the dvi file when finished."
  (let (filename queued oldfiles snippet)
    (dolist (ov (prog1 preview-gs-queue (setq preview-gs-queue nil)))
      (when (and (setq queued (overlay-get ov 'queued))
                 (setq snippet (aref (overlay-get ov 'queued) 2))
                 (setq filename (preview-make-filename
                                 (format "prev%03d.%s"
                                         snippet preview-dvipng-image-type)
                                 TeX-active-tempdir)))
        (if (file-exists-p (car filename))
            (progn
              (overlay-put ov 'filenames (list filename))
              (preview-replace-active-icon
               ov
               (preview-create-icon (car filename)
                                    preview-dvipng-image-type
                                    (preview-ascent-from-bb
                                     (aref queued 0))
                                    (aref preview-colors 2)))
              (overlay-put ov 'queued nil))
          (push filename oldfiles)
          (overlay-put ov 'filenames nil)
          (push ov preview-gs-queue))))
    (if (setq preview-gs-queue (nreverse preview-gs-queue))
        (progn
          (preview-start-dvips preview-fast-conversion)
          (setq TeX-sentinel-function (lambda (process command)
                                        (preview-gs-dvips-sentinel
                                         process
                                         command
                                         t)))
          (dolist (ov preview-gs-queue)
            (setq snippet (aref (overlay-get ov 'queued) 2))
            (overlay-put ov 'filenames
                         (list
                          (preview-make-filename
                           (or preview-ps-file
                               (format "preview.%03d" snippet))
                           TeX-active-tempdir))))
          (while (setq filename (pop oldfiles))
            (condition-case nil
                (preview-delete-file filename)
              (file-error nil))))
      (condition-case nil
          (let ((gsfile preview-gs-file))
            (delete-file
             (with-current-buffer TeX-command-buffer
               (funcall (car gsfile) "dvi" t))))
        (file-error nil)))))

(defun preview-active-string (ov)
  "Generate before-string for active image overlay OV."
  (preview-make-clickable
   (overlay-get ov 'preview-map)
   (car (overlay-get ov 'preview-image))
   "%s opens text
%s more options"))

(defun preview-make-filename (file tempdir)
  "Generate a preview filename from FILE and TEMPDIR.
Filenames consist of a CONS-cell with absolute file name as CAR
and TEMPDIR as CDR.  TEMPDIR is a copy of `TeX-active-tempdir'
with the directory name, the reference count and its top directory
name elements.  If FILE is already in that form, the file name itself
gets converted into a CONS-cell with a name and a reference count."
  (if (consp file)
      (progn
        (if (consp (car file))
            (setcdr (car file) (1+ (cdr (car file))))
          (setcar file (cons (car file) 1)))
        file)
    (setcar (nthcdr 2 tempdir) (1+ (nth 2 tempdir)))
    (cons (expand-file-name file (nth 0 tempdir))
          tempdir)))

(defun preview-attach-filename (attached file)
  "Attaches the absolute file name ATTACHED to FILE."
  (if (listp (caar file))
      (setcar (car file) (cons attached (caar file)))
    (setcar (car file) (list attached (caar file))))
  file)

(defun preview-delete-file (file)
  "Delete a preview FILE.
See `preview-make-filename' for a description of the data
structure.  If the containing directory becomes empty,
it gets deleted as well."
  (let ((filename
         (if (consp (car file))
             (and (zerop
                   (setcdr (car file) (1- (cdr (car file)))))
                  (car (car file)))
           (car file))))
    (if filename
        (unwind-protect
            (if (listp filename)
                (dolist (elt filename) (delete-file elt))
              (delete-file filename))
          (let ((tempdir (cdr file)))
            (when tempdir
              (if (> (nth 2 tempdir) 1)
                  (setcar (nthcdr 2 tempdir) (1- (nth 2 tempdir)))
                (setcdr file nil)
                (delete-directory (nth 0 tempdir)))))))))

(defvar preview-buffer-has-counters nil)
(make-variable-buffer-local 'preview-buffer-has-counters)

(defun preview-place-preview (snippet start end
                                      box counters tempdir place-opts)
  "Generate and place an overlay preview image.
This generates the filename for the preview
snippet SNIPPET in the current buffer, and uses it for the
region between START and END.  BOX is an optional preparsed
TeX bounding BOX passed on to the `place' hook.
COUNTERS is the info about saved counter structures.
TEMPDIR is a copy of `TeX-active-tempdir'.
PLACE-OPTS are additional arguments passed into
`preview-parse-messages'.  Returns
a list with additional info from the placement hook.
Those lists get concatenated together and get passed
to the close hook."
  (preview-clearout start end tempdir)
  (let ((ov (make-overlay start end nil nil nil)))
    (overlay-put ov 'priority (TeX-overlay-prioritize start end))
    (overlay-put ov 'preview-map
                 (preview-make-clickable
                  nil nil nil
                  (lambda (event) (interactive "e")
                    (preview-toggle ov 'toggle event))
                  (lambda (event) (interactive "e")
                    (preview-context-menu ov event))))
    (overlay-put ov 'timestamp tempdir)
    (when (cdr counters)
      (overlay-put ov 'preview-counters counters)
      (setq preview-buffer-has-counters t))
    (prog1 (apply #'preview-call-hook 'place ov snippet box
                  place-opts)
      (overlay-put ov 'strings
                   (list (preview-active-string ov)))
      (preview-toggle ov t))))

(defun preview-counter-find (begin)
  "Fetch the next preceding or next preview-counters property.
Factored out because of compatibility macros XEmacs would
not use in advice."
  (or (car (get-char-property begin 'preview-counters))
      (cdr (get-char-property (max (point-min)
                                   (1- begin))
                              'preview-counters))
      (cdr (get-char-property
            (max (point-min)
                 (1- (previous-single-char-property-change
                      begin
                      'preview-counters)))
            'preview-counters))
      (car (get-char-property
            (next-single-char-property-change begin 'preview-counters)
            'preview-counters))))

(defun preview--counter-information (begin)
  "Return repeated \\setcounter declaration based on point BEGIN.
If `preview-buffer-has-counters' is non-nil, return string to
insert into region tex file containing as many
\\setcounter{COUNTER}{VALUE} as possible built from
`preview-counters' property near the point BEGIN.  Otherwise,
return nil."
  (if preview-buffer-has-counters
      (mapconcat
       #'identity
       (cons
        ""
        (preview-counter-find begin))
       "\\setcounter")))

(defun preview-reinstate-preview (tempdirlist timestamp start end
  image filename &optional counters)
  "Reinstate a single preview.
This gets passed TEMPDIRLIST, a list consisting of the kind
of entries used in `TeX-active-tempdir', and TIMESTAMP, the
time stamp under which the file got read in.  It returns an augmented
list.  START and END give the buffer location where the preview
is to be situated, IMAGE the image to place there, and FILENAME
the file to use: a triple consisting of filename, its temp directory
and the corresponding topdir.  COUNTERS is saved counter information,
if any."
  (when
      (or (null filename) (file-readable-p (car filename)))
    (when filename
      (unless (equal (nth 1 filename) (car TeX-active-tempdir))
        (setq TeX-active-tempdir
              (or (assoc (nth 1 filename) tempdirlist)
                  (car (push (append (cdr filename) (list 0))
                             tempdirlist))))
        (setcar (cdr TeX-active-tempdir)
                (car (or (member (nth 1 TeX-active-tempdir)
                                 preview-temp-dirs)
                         (progn
                           (add-hook 'kill-emacs-hook
                                     #'preview-cleanout-tempfiles t)
                           (push (nth 1 TeX-active-tempdir)
                                 preview-temp-dirs))))))
      (setcar (nthcdr 2 TeX-active-tempdir)
              (1+ (nth 2 TeX-active-tempdir)))
      (setcdr filename TeX-active-tempdir)
      (setq filename (list filename)))
    (let ((ov (make-overlay start end nil nil nil)))
      (overlay-put ov 'priority (TeX-overlay-prioritize start end))
      (overlay-put ov 'preview-map
                   (preview-make-clickable
                    nil nil nil
                    (lambda (event) (interactive "e")
                      (preview-toggle ov 'toggle event))
                    (lambda (event) (interactive "e")
                      (preview-context-menu ov event))))
      (when counters
        (overlay-put
         ov 'preview-counters
         (cons
            (mapcar #'cdr
                    (if (string= (car counters) "")
                        preview-parsed-counters
                      (setq preview-parsed-counters
                            (preview-parse-counters (car counters)))))
            (mapcar #'cdr
                    (if (string= (cdr counters) "")
                        preview-parsed-counters
                      (setq preview-parsed-counters
                            (preview-parse-counters (cdr counters)))))))
        (setq preview-buffer-has-counters t))
      (overlay-put ov 'filenames filename)
      (overlay-put ov 'preview-image (cons (preview-import-image image)
                                           image))
      (overlay-put ov 'strings
                   (list (preview-active-string ov)))
      (overlay-put ov 'timestamp timestamp)
      (preview-toggle ov t)))
  tempdirlist)

(defun preview-back-command (&optional nocomplex)
  "Move backward a TeX token.
If NOCOMPLEX is set, only basic tokens and no argument sequences
will be skipped over backwards."
  (let ((oldpos (point)) oldpoint)
    (condition-case nil
        (or (search-backward-regexp "\\(\\$\\$?\
\\|\\\\[^a-zA-Z@]\
\\|\\\\[a-zA-Z@]+\
\\|\\\\begin[ \t]*{[^}]+}\
\\)\\=" (line-beginning-position) t)
            nocomplex
            (if (eq ?\) (char-syntax (char-before)))
                (while
                    (progn
                      (setq oldpoint (point))
                      (backward-sexp)
                      (and (not (eq oldpoint (point)))
                           (eq ?\( (char-syntax (char-after))))))
              (backward-char)))
      (error (goto-char oldpos)))))

(defcustom preview-required-option-list '("active" "tightpage" "auctex"
                                          (preview-preserve-counters
                                           "counters"))
  "Specifies required options passed to the preview package.
These are passed regardless of whether there is an explicit
\\usepackage of that package present."
  :group 'preview-latex
  :type preview-expandable-string)

(defcustom preview-preserve-counters nil
  "Try preserving counters for partial runs if set."
  :group 'preview-latex
  :type 'boolean)

(defcustom preview-default-option-list '("displaymath" "floats"
                                         "graphics" "textmath" "sections"
                                         "footnotes")
  "Specifies default options to pass to preview package.
These options are only used when the LaTeX document in question does
not itself load the preview package, namely when you use preview
on a document not configured for preview.  \"auctex\", \"active\",
\"dvips\" and \"delayed\" need not be specified here."
  :group 'preview-latex
  :type '(list (set :inline t :tag "Options known to work"
                    :format "%t:\n%v%h" :doc
                    "The above options are all the useful ones
at the time of the release of this package.
You should not need \"Other options\" unless you
upgraded to a fancier version of just the LaTeX style.
Please also note that `psfixbb' fails to have an effect if
`preview-fast-conversion' or `preview-prefer-TeX-bb'
are selected."
                    (const "displaymath")
                    (const "floats")
                    (const "graphics")
                    (const "textmath")
                    (const "sections")
                    (const "footnotes")
                    (const "showlabels")
                    (const "psfixbb"))
               (set :tag "Expert options" :inline t
                    :format "%t:\n%v%h" :doc
                    "Expert options should not be enabled permanently."
                    (const "noconfig")
                    (const "showbox")
                    (const "tracingall"))
               (repeat :inline t :tag "Other options" (string))))

(defcustom preview-default-preamble
  '("\\RequirePackage[" ("," . preview-default-option-list)
                                      "]{preview}[2004/11/05]")
  "Specifies default preamble code to add to a LaTeX document.
If the document does not itself load the preview package, that is,
when you use preview on a document not configured for preview, this
list of LaTeX commands is inserted just before \\begin{document}."
  :group 'preview-latex
  :type preview-expandable-string)

(defcustom preview-LaTeX-command '("%`%l \"\\nonstopmode\\nofiles\
\\PassOptionsToPackage{" ("," . preview-required-option-list) "}{preview}\
\\AtBeginDocument{\\ifx\\ifPreview\\undefined"
preview-default-preamble "\\fi}\"%' \"\\detokenize{\" %(t-filename-only) \"}\"")
  ;; Since TeXLive 2018, the default encoding for LaTeX files has been
  ;; changed to UTF-8 if used with classic TeX or pdfTeX.  I.e.,
  ;; \usepackage[utf8]{inputenc} is enabled by default in (pdf)latex.
  ;; c.f. LaTeX News issue 28
  ;; Due to this change, \detokenize is required to recognize
  ;; non-ascii characters in the file name when \input is supplemented
  ;; implicitly by %`-%' pair.
  "Command used for starting a preview.
See description of `TeX-command-list' for details."
  :group 'preview-latex
  :type preview-expandable-string)

(defun preview-goto-info-page ()
  "Read documentation for preview-latex in the info system."
  (interactive)
  (info "(preview-latex)"))

(eval-after-load 'info '(add-to-list 'Info-file-list-for-emacs
                                     '("preview" . "preview-latex")))

(defvar preview-map
  (let ((map (make-sparse-keymap)))
    (define-key map "\C-p" #'preview-at-point)
    (define-key map "\C-r" #'preview-region)
    (define-key map "\C-b" #'preview-buffer)
    (define-key map "\C-d" #'preview-document)
    (define-key map "\C-f" #'preview-cache-preamble)
    (define-key map "\C-c\C-f" #'preview-cache-preamble-off)
    (define-key map "\C-i" #'preview-goto-info-page)
    ;;  (define-key map "\C-q" #'preview-paragraph)
    (define-key map "\C-e" #'preview-environment)
    (define-key map "\C-s" #'preview-section)
    (define-key map "\C-w" #'preview-copy-region-as-mml)
    (define-key map "\C-c\C-p" #'preview-clearout-at-point)
    (define-key map "\C-c\C-r" #'preview-clearout)
    (define-key map "\C-c\C-s" #'preview-clearout-section)
    (define-key map "\C-c\C-b" #'preview-clearout-buffer)
    (define-key map "\C-c\C-d" #'preview-clearout-document)
    map))

(defun preview-copy-text (ov)
  "Copy the text of OV into the kill buffer."
  (with-current-buffer (overlay-buffer ov)
    (copy-region-as-kill (overlay-start ov) (overlay-end ov))))

(defun preview-copy-mml (ov)
  "Copy an MML representation of OV into the kill buffer.
This can be used to send inline images in mail and news when
using MML mode."
  (when (catch 'badcolor
          (let ((str (car (preview-format-mml ov))))
            (if str
                (if (eq last-command #'kill-region)
                    (kill-append str nil)
                  (kill-new str))
              (error "No image file available")))
          nil)
    (let (preview-transparent-border)
      (preview-regenerate ov))))

(defun preview-copy-region-as-mml (start end)
  (interactive "r")
  (when (catch 'badcolor
          (let (str lst dont-ask)
            (dolist (ov (overlays-in start end))
              (when (setq str (preview-format-mml ov dont-ask))
                (setq dont-ask (cdr str))
                (and
                 (>= (overlay-start ov) start)
                 (<= (overlay-end ov) end)
                 (push (list (- (overlay-start ov) start)
                             (- (overlay-end ov) start)
                             (car str)) lst))))
            (setq str (buffer-substring start end))
            (dolist (elt (nreverse (sort lst #'car-less-than-car)))
              (setq str (concat (substring str 0 (nth 0 elt))
                                (nth 2 elt)
                                (substring str (nth 1 elt)))))
            (if (eq last-command #'kill-region)
                (kill-append str nil)
              (kill-new str)))
          nil)
    (let (preview-transparent-border)
      (preview-region start end))))

(autoload 'mailcap-extension-to-mime "mailcap")

(defun preview-format-mml (ov &optional dont-ask)
  "Return an MML representation of OV as string.
This can be used to send inline images in mail and news when
using MML mode.  If there is nothing current available,
NIL is returned.  If the image has a colored border and the
user wants it removed when asked (unless DONT-ASK is set),
'badcolor is thrown a t.  The MML is returned in the car of the
result, DONT-ASK in the cdr."
  (and (memq (overlay-get ov 'preview-state) '(active inactive))
       (not (overlay-get ov 'queued))
       (let* ((text (with-current-buffer (overlay-buffer ov)
                     (buffer-substring (overlay-start ov)
                                       (overlay-end ov))))
              (image (cdr (overlay-get ov 'preview-image)))
              file type)
         (cond ((consp image)
                (and (not dont-ask)
                     (nth 3 image)
                     (if (y-or-n-p "Replace colored borders? ")
                         (throw 'badcolor t)
                       (setq dont-ask t)))
                (setq file (car (car (last (overlay-get ov 'filenames))))
                      type (mailcap-extension-to-mime
                            (file-name-extension file)))
                (cons
                 (format "<#part %s
description=\"%s\"
filename=%s>
<#/part>"
                         (if type
                             (format "type=\"%s\" disposition=inline" type)
                           "disposition=attachment")
                         (if (string-match "[\n\"]" text)
                             "preview-latex image"
                           text)
                         (if (string-match "[ \n<>]" file)
                             (concat "\"" file "\"")
                           file))
                 dont-ask))
               ((stringp image)
                (cons image dont-ask))))))

(defun preview-active-contents (ov)
  "Check whether we have a valid image associated with OV."
  (and (memq (overlay-get ov 'preview-state) '(active inactive)) t))

(defun preview-context-menu (ov ev)
  "Pop up a menu for OV at position EV."
  (popup-menu
   `("Preview"
     ["Toggle" (preview-toggle ,ov 'toggle ',ev)
      (preview-active-contents ,ov)]
     ["Regenerate" (preview-regenerate ,ov)]
     ["Remove" (preview-delete ,ov)]
     ["Copy text" (preview-copy-text ,ov)]
     ["Copy MIME" (preview-copy-mml ,ov)
      (preview-active-contents ,ov)])
   ev))

(defvar preview-TeX-style-dir)

(defun preview-TeX-style-cooked ()
  "Return `preview-TeX-style-dir' in cooked form.
This will be fine for prepending to a `TEXINPUTS' style
environment variable, including an initial `.' at the front."
  (if (or (zerop (length preview-TeX-style-dir))
          (member (substring preview-TeX-style-dir -1) '(";" ":")))
      preview-TeX-style-dir
    (let ((sep
           (cond
            ((stringp TeX-kpathsea-path-delimiter)
             TeX-kpathsea-path-delimiter)
            ((string-match
              "\\`.[:]"
              (if (file-name-absolute-p preview-TeX-style-dir)
                  preview-TeX-style-dir
                (expand-file-name preview-TeX-style-dir)))
             ";")
            (t ":"))))
      (concat "." sep preview-TeX-style-dir sep))))

(defun preview-set-texinputs (&optional remove)
  "Add `preview-TeX-style-dir' into `TEXINPUTS' variables.
With prefix argument REMOVE, remove it again."
  (interactive "P")
  (let ((case-fold-search nil)
        (preview-TeX-style-dir (preview-TeX-style-cooked))
        pattern)
    (if remove
        (progn
          (setq pattern (concat "\\`\\(TEXINPUTS[^=]*\\)=\\(.*\\)"
                                (regexp-quote preview-TeX-style-dir)))
          (dolist (env (copy-sequence process-environment))
            (if (string-match pattern env)
                (setenv (match-string 1 env)
                        (and (or (< (match-beginning 2) (match-end 2))
                                 (< (match-end 0) (length env)))
                             (concat (match-string 2 env)
                                     (substring env (match-end 0))))))))
      (setq pattern (regexp-quote preview-TeX-style-dir))
      (dolist (env (cons "TEXINPUTS=" (copy-sequence process-environment)))
        (if (string-match "\\`\\(TEXINPUTS[^=]*\\)=" env)
            (unless (save-match-data (string-match pattern env))
              (setenv (match-string 1 env)
                      (concat preview-TeX-style-dir
                              (substring env (match-end 0))))))))))

(defcustom preview-TeX-style-dir nil
  "This variable contains the location of uninstalled TeX styles.
If this is nil, the preview styles are considered to be part of
the installed TeX system.

Otherwise, it can either just specify an absolute directory, or
it can be a complete TEXINPUTS specification.  If it is the
latter, it has to be followed by the character with which
kpathsea separates path components, either `:' on Unix-like
systems, or `;' on Windows-like systems.  And it should be
preceded with .: or .; accordingly in order to have . first in
the search path.

The `TEXINPUTS' environment type variables will get this prepended
at load time calling \\[preview-set-texinputs] to reflect this.
You can permanently install the style files using
\\[preview-install-styles].

Don't set this variable other than with customize so that its
changes get properly reflected in the environment."
  :group 'preview-latex
  :set (lambda (var value)
         (and (boundp var)
              (symbol-value var)
              (preview-set-texinputs t))
         (set var value)
         (and (symbol-value var)
              (preview-set-texinputs)))
  :type '(choice (const :tag "Installed" nil)
                 (string :tag "Style directory or TEXINPUTS path")))

;;;###autoload
(defun preview-install-styles (dir &optional force-overwrite
                                   force-save)
  "Installs the TeX style files into a permanent location.
This must be in the TeX search path.  If FORCE-OVERWRITE is greater
than 1, files will get overwritten without query, if it is less
than 1 or nil, the operation will fail.  The default of 1 for interactive
use will query.

Similarly FORCE-SAVE can be used for saving
`preview-TeX-style-dir' to record the fact that the uninstalled
files are no longer needed in the search path."
  (interactive "DPermanent location for preview TeX styles
pp")
  (unless preview-TeX-style-dir
    (error "Styles are already installed"))
  (dolist (file (or
                 (condition-case nil
                     (directory-files
                      (progn
                        (string-match
                         "\\`\\(\\.[:;]\\)?\\(.*?\\)\\([:;]\\)?\\'"
                         preview-TeX-style-dir)
                        (match-string 2 preview-TeX-style-dir))
                      t "\\.\\(sty\\|def\\|cfg\\)\\'")
                   (error nil))
                 (error "Can't find files to install")))
    (copy-file file dir (cond ((eq force-overwrite 1) 1)
                              ((numberp force-overwrite)
                               (> force-overwrite 1))
                              (t force-overwrite))))
  (if (cond ((eq force-save 1)
             (y-or-n-p "Stop using non-installed styles permanently "))
            ((numberp force-save)
             (> force-save 1))
            (t force-save))
      (customize-save-variable 'preview-TeX-style-dir nil)
    (customize-set-variable 'preview-TeX-style-dir nil)))

(defun preview-mode-setup ()
  "Setup proper buffer hooks and behavior for previews."
  (set (make-local-variable 'desktop-save-buffer)
       #'desktop-buffer-preview-misc-data)
  (add-hook 'pre-command-hook #'preview-mark-point nil t)
  (add-hook 'post-command-hook #'preview-move-point nil t)
  (unless preview-tb-icon
    (setq preview-tb-icon (preview-filter-specs preview-tb-icon-specs)))
  (when preview-tb-icon
    (define-key LaTeX-mode-map [tool-bar preview]
      `(menu-item "Preview at point" preview-at-point
                  :image ,preview-tb-icon
                  :help "Preview on/off at point"
                  :vert-only t)))
  (when buffer-file-name
    (let* ((filename (expand-file-name buffer-file-name))
           format-cons)
      (when (string-match (concat "\\." TeX-default-extension "\\'")
                          filename)
        (setq filename (substring filename 0 (match-beginning 0))))
      (setq format-cons (assoc filename preview-dumped-alist))
      (when (consp (cdr format-cons))
        (preview-unwatch-preamble format-cons)
        (preview-watch-preamble (current-buffer)
                                (cadr format-cons)
                                format-cons)))))

;;;###autoload
(defun LaTeX-preview-setup ()
  "Hook function for embedding the preview package into AUCTeX.
This is called by `LaTeX-mode-hook' and changes AUCTeX variables
to add the preview functionality."
  ;; This has to be done only once.
  (unless (and (boundp 'LaTeX-mode-hook)
               (memq #'preview-mode-setup LaTeX-mode-hook))
    (remove-hook 'LaTeX-mode-hook #'LaTeX-preview-setup)
    (add-hook 'LaTeX-mode-hook #'preview-mode-setup)
    (define-key LaTeX-mode-map "\C-c\C-p" preview-map)
    (easy-menu-define preview-menu LaTeX-mode-map
      "This is the menu for preview-latex."
      '("Preview"
        "Generate previews"
        ["(or toggle) at point" preview-at-point]
        ["for environment" preview-environment]
        ["for section" preview-section]
        ["for region" preview-region mark-active]
        ["for buffer" preview-buffer]
        ["for document" preview-document]
        "---"
        "Remove previews"
        ["at point" preview-clearout-at-point]
        ["from section" preview-clearout-section]
        ["from region" preview-clearout mark-active]
        ["from buffer" preview-clearout-buffer]
        ["from document" preview-clearout-document]
        "---"
        "Turn preamble cache"
        ["on" preview-cache-preamble]
        ["off" preview-cache-preamble-off]
        "---"
        ("Customize"
         ["Browse options"
          (customize-group 'preview)]
         ["Extend this menu"
          (easy-menu-add-item
           nil '("Preview")
           (customize-menu-create 'preview))])
        ["Read documentation" preview-goto-info-page]
        ["Report Bug" preview-report-bug]))
    (if (eq major-mode 'latex-mode)
        (preview-mode-setup))
    (if (boundp 'desktop-buffer-misc)
        (preview-buffer-restore desktop-buffer-misc))))

(defun preview-clean-subdir (dir)
  "Cleans out a temporary DIR with preview image files."
  (condition-case err
      (progn
        (mapc #'delete-file
              (directory-files dir t "\\`pr" t))
        (delete-directory dir))
    (error (message "Deletion of `%s' failed: %s" dir
                    (error-message-string err)))))

(defun preview-clean-topdir (topdir)
  "Cleans out TOPDIR from temporary directories.
This does not erase the directory itself since its permissions
might be needed for colloborative work on common files."
  (mapc #'preview-clean-subdir
        (condition-case nil
            (directory-files topdir t "\\`tmp" t)
          (file-error nil))))

(defun preview-create-subdirectory ()
  "Create a temporary subdir for the current TeX process.
If necessary, generates a fitting top
directory or cleans out an existing one (if not yet
visited in this session), then returns the name of
the created subdirectory relative to the master directory,
in shell-quoted form.  `TeX-active-tempdir' is
set to the corresponding TEMPDIR descriptor as described
in `preview-make-filename'.  The directory is registered
in `preview-temp-dirs' in order not to be cleaned out
later while in use."
  (let ((topdir (expand-file-name (TeX-active-master "prv"))))
    (if (file-directory-p topdir)
        (unless (member topdir preview-temp-dirs)
          ;;  Cleans out the top preview directory by
          ;;  removing subdirs possibly left from a previous session.
          (preview-clean-topdir topdir)
          (push topdir preview-temp-dirs))
      (make-directory topdir)
      (add-to-list 'preview-temp-dirs topdir))
    (add-hook 'kill-emacs-hook #'preview-cleanout-tempfiles t)
    (setq TeX-active-tempdir
          (list (make-temp-file (expand-file-name
                                 "tmp" (file-name-as-directory topdir)) t)
                topdir
                0))
    (shell-quote-argument
     (concat (file-name-as-directory
              ;; Don't use topdir, because %m expects the path to be
              ;; relative to master
              (TeX-active-master "prv" t))
             (file-name-nondirectory (nth 0 TeX-active-tempdir))))))

(defun preview-parse-counters (string)
  "Extract counter information from STRING."
  (let ((list preview-parsed-counters) (pos 0))
    (while (eq pos (string-match " *\\({\\([^{}]+\\)}{[-0-9]+}\\)" string pos))
      (setcdr (or (assoc (match-string 2 string) list)
                  (car (push (list (match-string 2 string)) list)))
              (match-string 1 string))
      (setq pos (match-end 1)))
    list))

(defun preview-parse-tightpage (string)
  "Build tightpage vector from STRING,"
  (read (concat "[" string "]")))

(defvar preview-parse-variables
  '(("Fontsize" preview-parsed-font-size
     "\\` *\\([0-9.]+\\)pt\\'" 1 string-to-number)
    ("Magnification" preview-parsed-magnification
     "\\` *\\([0-9]+\\)\\'" 1 string-to-number)
    ("PDFoutput" preview-parsed-pdfoutput
     "" 0 stringp)
    ("Counters" preview-parsed-counters
     ".*" 0 preview-parse-counters)
    ("Tightpage" preview-parsed-tightpage
     "\\` *\\(-?[0-9]+ *\\)\\{4\\}\\'" 0 preview-parse-tightpage)))

(defun preview-error-quote (string)
  "Turn STRING with potential ^^ sequences into a regexp.
To preserve sanity, additional ^ prefixes are matched literally,
so the character represented by ^^^ preceding extended characters
will not get matched, usually."
  (let (output case-fold-search)
    ;; Some coding systems (e.g. japanese-shift-jis) use regexp meta
    ;; characters on encoding.  Such meta characters would be
    ;; interfered with `regexp-quote' below.  Thus the idea of
    ;; "encoding entire string beforehand and decoding it at the last
    ;; stage" does not work for such coding systems.
    ;; Rather, we work consistently with decoded text.

    ;; Bytes with value from 0x80 to 0xFF represented with ^^ form are
    ;; converted to byte sequence, and decoded by the file coding
    ;; system.
    (setq string
          (preview--decode-^^ab string buffer-file-coding-system))

    ;; Then, control characters are taken into account.
    (while (string-match "\\^\\{2,\\}\\([@-_?]\\)" string)
      (setq output
            (concat output
                    (regexp-quote (substring string
                                             0
                                             (- (match-beginning 1) 2)))
                    (concat
                     "\\(?:" (regexp-quote
                              (substring string
                                         (- (match-beginning 1) 2)
                                         (match-end 0)))
                     "\\|"
                     (char-to-string
                      (logxor (aref string (match-beginning 1)) 64))
                     "\\)"))
            string (substring string (match-end 0))))
    (setq output (concat output (regexp-quote string)))
    output))

(defun preview--decode-^^ab (string coding-system)
  "Decode ^^ sequences in STRING with CODING-SYSTEM.
Sequences of control characters such as ^^I are left untouched.

Return a new string."
  ;; Since the given string can contain multibyte characters, decoding
  ;; should be performed seperately on each segment made up entirely
  ;; with ASCII and raw 8-bit characters.
  ;; Raw 8-bit characters can arise if the latex outputs multibyte
  ;; characters with partial ^^-quoting.
  (let ((result ""))
    ;; Here we want to collect all the ASCII and raw 8-bit bytes,
    ;; excluding proper multibyte characters.  The regexp
    ;; [^[:multibyte:]]+ serves for that purpose.  The alternative
    ;; [\x00-\xFF]+ does the job as well at least for emacs 24-26, so
    ;; use it instead if the former becomes invalid in future.
    ;; N.B. [[:unibyte:]]+ doesn't match raw 8-bit bytes, contrary to
    ;; naive expectation.
    (while (string-match "[^[:multibyte:]]+" string)
      (setq result
            (concat result
                    (substring string 0 (match-beginning 0))
                    (let ((text
                           (save-match-data
                             (preview--convert-^^ab
                              (match-string 0 string)))))
                      (decode-coding-string text coding-system)))
            string (substring string (match-end 0))))
    (setq result (concat result string))
    result))

(defun preview--convert-^^ab (string)
  "Convert ^^ sequences in STRING to raw 8bit.
Sequences of control characters such as ^^I are left untouched.

Return a new string."
  (let ((result ""))
    (while (string-match "\\^\\^[8-9a-f][0-9a-f]" string)
      (setq result
            (concat result
                    (substring string 0 (match-beginning 0))
                    (let ((byte (string-to-number
                                 (substring string
                                            (+ (match-beginning 0) 2)
                                            (match-end 0)) 16)))
                      (byte-to-string byte)))
            string (substring string (match-end 0))))
    (setq result (concat result string))
    result))

(defun preview-parse-messages (open-closure)
  "Turn all preview snippets into overlays.
This parses the pseudo error messages from the preview
document style for LaTeX.  OPEN-CLOSURE is called once
it is certain that we have a valid output file, and it has
to return in its CAR the PROCESS parameter for the CLOSE
call, and in its CDR the final stuff for the placement hook."
  (with-temp-message "locating previews..."
    (let (TeX-error-file TeX-error-offset snippet box counters
          file line
          (lsnippet 0) lstart (lfile "") lline lbuffer lpoint
          lcounters
          string after-string
          offset
          parsestate (case-fold-search nil)
          (run-buffer (current-buffer))
          (run-directory default-directory)
          tempdir
          close-data
          open-data
          fast-hook
          slow-hook
          TeX-translate-location-file
          TeX-translate-location-line
          TeX-translate-location-error
          TeX-translate-location-offset
          TeX-translate-location-context
          TeX-translate-location-string)
      ;; clear parsing variables
      (dolist (var preview-parse-variables)
        (set (nth 1 var) nil))
      (goto-char (point-min))
      (unwind-protect
          (progn
            (while
                (re-search-forward "\
^\\(!\\|\\(.*?\\):[0-9]+:\\) \\|\
\(\\(/*\
\\(?:\\.+[^()\r\n{} /]*\\|[^()\r\n{} ./]+\
\\(?: [^()\r\n{} ./]+\\)*\\(?:\\.[-0-9a-zA-Z_.]*\\)?\\)\
\\(?:/+\\(?:\\.+[^()\r\n{} /]*\\|[^()\r\n{} ./]+\
\\(?: [^()\r\n{} ./]+\\)*\\(?:\\.[-0-9a-zA-Z_.]*\\)?\\)?\\)*\\)\
)*\\(?: \\|\r?$\\)\\|\
\\()+\\)\\|\
 !\\(?:offset(\\([---0-9]+\\))\\|\
name(\\([^)]+\\))\\)\\|\
^Preview: \\([a-zA-Z]+\\) \\([^\n\r]*\\)\r?$" nil t)
;;;   Ok, here is a line by line breakdown:
;;;   match-alternative 1:
;;;   error indicator for TeX error, either style.
;;;   match-alternative 2:
;;;   The same, but file-line-error-style, matching on file name.
;;;   match-alternative 3:
;;;   Too ugly to describe in detail.  In short, we try to catch file
;;;   names built from path components that don't contain spaces or
;;;   other special characters once the file extension has started.
;;;
;;;   Position for searching immediately after the file name so as to
;;;   not miss closing parens or something.
;;;   (match-string 3) is the file name.
;;;   match-alternative 4:
;;;   )+\( \|$\)
;;;   a closing paren followed by the end of line or a space: a just
;;;   closed file.
;;;   match-alternative 5 (wrapped into one shy group with
;;;   match-alternative 6, so that the match on first char is slightly
;;;   faster):
;;;   !offset(\([---0-9]+\))
;;;   an AUCTeX offset message. (match-string 5) is the offset itself
;;;   !name(\([^)]+\))
;;;   an AUCTeX file name message.  (match-string 6) is the file name
;;;   TODO: Actually, the latter two should probably again match only
;;;   after a space or newline, since that it what \message produces.
;;;  disabled in prauctex.def:
;;;  \(?:Ov\|Und\)erfull \\.*[0-9]*--[0-9]*
;;;  \(?:.\{79\}
;;;  \)*.*$\)\|
;;;   This would have caught overfull box messages that consist of
;;;   several lines of context all with 79 characters in length except
;;;   of the last one.  prauctex.def kills all such messages.
              (setq file (match-string-no-properties 2))
              (cond
               ((match-beginning 1)
                (if (looking-at "\
\\(?:Preview\\|Package Preview Error\\): Snippet \\([---0-9]+\\) \\(started\\|ended\\(\
\\.? *(\\([---0-9]+\\)\\+\\([---0-9]+\\)x\\([---0-9]+\\))\\)?\\)\\.")
                    (progn
                      (when file
                        (unless TeX-error-file
                          (push nil TeX-error-file)
                          (push nil TeX-error-offset))
                        (unless (car TeX-error-offset)
                          (rplaca TeX-error-file file)))
                      (setq snippet (string-to-number (match-string 1))
                            box (unless
                                    (string= (match-string 2) "started")
                                  (if (match-string 4)
                                      (mapcar #'(lambda (x)
                                                  (* (preview-get-magnification)
                                                     (string-to-number x)))
                                              (list
                                               (match-string 4)
                                               (match-string 5)
                                               (match-string 6)))
                                    t))
                            counters (mapcar #'cdr preview-parsed-counters)

                            ;; And the line number to position the cursor.
                            line (progn
                                   (setq lpoint (point))
                                   (end-of-line)
;;;  variant 1: profiling seems to indicate the regexp-heavy solution
;;;  to be favorable.  Removing incomplete characters from the error
;;;  context is an absolute nuisance.
                                   (and (re-search-forward "\
^l\\.\\([0-9]+\\) \\(\\.\\.\\.\\(?:\\^*\\(?:[89a-f][0-9a-f]\\|[]@-\\_?]\\)\\|\
\[0-9a-f]?\\)\\)?\\([^\n\r]*?\\)\r?
\\([^\n\r]*?\\)\\(\\(?:\\^+[89a-f]?\\)?\\.\\.\\.\\)?\r?$" nil t)
                                        (string-to-number (match-string 1))))
                            ;; And a string of the context to search for.
                            string (and line (match-string 3))
                            after-string (and line (buffer-substring
                                                    (+ (match-beginning 4)
                                                       (- (match-end 3)
                                                          (match-beginning 0)))
                                                    (match-end 4)))

                            ;; We may use these in another buffer.
                            offset (or (car TeX-error-offset) 0)
                            file (car TeX-error-file))
                      (when (and (stringp file)
                                 (or (string= file "<none>")
                                     (TeX-match-extension file)))
                        ;; if we are the first time round, check for fast hooks:
                        (when (null parsestate)
                          (setq open-data
                                (save-excursion (funcall open-closure))
                                tempdir TeX-active-tempdir)
                          (dolist
                              (lst (if (listp TeX-translate-location-hook)
                                       TeX-translate-location-hook
                                     (list TeX-translate-location-hook)))
                            (let ((fast
                                   (and (symbolp lst)
                                        (get lst 'TeX-translate-via-list))))
                              (if fast
                                  (setq fast-hook
                                        (nconc fast-hook (list fast)))
                                (setq slow-hook
                                      (nconc slow-hook (list lst)))))))
                        ;; Functions in `TeX-translate-location-hook'
                        ;; may examine and modify the following variables.
                        (setq TeX-translate-location-file file
                              TeX-translate-location-line line
                              ;; TeX-translate-location-error error
                              TeX-translate-location-offset offset
                              ;; TeX-translate-location-context context
                              TeX-translate-location-string string)
                        (condition-case err
                            (save-excursion (mapc #'funcall slow-hook))
                          (error (preview-log-error err "Translation hook")))
                        (setq file TeX-translate-location-file
                              line TeX-translate-location-line
                              ;; error TeX-translate-location-error
                              offset TeX-translate-location-offset
                              ;; context TeX-translate-location-context
                              string TeX-translate-location-string)
                        (push (vector file (+ line offset)
                                      string after-string
                                      snippet box counters)
                              parsestate)))
                  ;; else normal error message
                  (forward-line)
                  (re-search-forward "^l\\.[0-9]" nil t)
                  (forward-line 2)))
               ((match-beginning 3)
                ;; New file -- Push on stack
                (push (match-string-no-properties 3) TeX-error-file)
                (push nil TeX-error-offset)
                (goto-char (match-end 3)))
               ((match-beginning 4)
                ;; End of file -- Pop from stack
                (when (> (length TeX-error-file) 1)
                  (pop TeX-error-file)
                  (pop TeX-error-offset))
                (goto-char (1+ (match-beginning 0))))
               ((match-beginning 5)
                ;; Hook to change line numbers
                (setq TeX-error-offset
                      (list (string-to-number (match-string 5)))))
               ((match-beginning 6)
                ;; Hook to change file name
                (setq TeX-error-file (list (match-string-no-properties 6))))
               ((match-beginning 7)
                (let ((var
                       (assoc (match-string-no-properties 7)
                              preview-parse-variables))
                      (offset (- (match-beginning 0) (match-beginning 8)))
                      (str (match-string-no-properties 8)))
                  ;; paste together continuation lines:
                  (while (= (- (length str) offset) 79)
                    (search-forward-regexp "^\\([^\n\r]*\\)\r?$")
                    (setq offset (- (length str))
                          str (concat str (match-string-no-properties 1))))
                  (when (and var
                             (string-match (nth 2 var) str))
                    (set (nth 1 var)
                         (funcall (nth 4 var)
                                  (match-string-no-properties
                                   (nth 3 var)
                                   str))))))))
            (when (null parsestate)
              (error "LaTeX found no preview images")))
        (unwind-protect
            (save-excursion
              (setq parsestate (nreverse parsestate))
              (condition-case err
                  (dolist (fun fast-hook)
                    (setq parsestate
                          (save-excursion (funcall fun parsestate))))
                (error (preview-log-error err "Fast translation hook")))
              (setq snippet 0)
              (dolist (state parsestate)
                (setq lsnippet snippet
                      file (aref state 0)
                      line (aref state 1)
                      string (aref state 2)
                      after-string (aref state 3)
                      snippet (aref state 4)
                      box (aref state 5)
                      counters (aref state 6))
                (unless (string= lfile file)
                  (set-buffer (if (string= file "<none>")
                                  (with-current-buffer run-buffer
                                    TeX-command-buffer)
                                (find-file-noselect
                                 (expand-file-name file run-directory))))
                  (setq lfile file))
                (save-excursion
                  (save-restriction
                    (widen)
                    ;; a fast hook might have positioned us already:
                    (if (number-or-marker-p string)
                        (progn
                          (goto-char string)
                          (setq lpoint
                                (if (number-or-marker-p after-string)
                                    after-string
                                  (line-beginning-position))))
                      (if (and (eq (current-buffer) lbuffer)
                               (<= lline line))
                          ;; while Emacs does the perfectly correct
                          ;; thing even when when the line differences
                          ;; get zero or negative, I don't trust this
                          ;; to be universally the case across other
                          ;; implementations.  Besides, if the line
                          ;; number gets smaller again, we are probably
                          ;; rereading the file, and restarting from
                          ;; the beginning will probably be faster.
                          (progn
                            (goto-char lpoint)
                            (if (/= lline line)
                                (if (eq selective-display t)
                                    (re-search-forward "[\n\C-m]" nil
                                                       'end
                                                       (- line lline))
                                  (forward-line (- line lline)))))
                        (goto-char (point-min))
                        (forward-line (1- line)))
                      (setq lpoint (point))
                      (cond
                       ((search-forward (concat string after-string)
                                        (line-end-position) t)
                        (backward-char (length after-string)))
                       ;;ok, transform ^^ sequences
                       ((search-forward-regexp
                         (concat "\\("
                                 (setq string
                                       (preview-error-quote
                                        string))
                                 "\\)"
                                 (setq after-string
                                       (preview-error-quote
                                        after-string)))
                         (line-end-position) t)
                        (goto-char (match-end 1)))
                       ((search-forward-regexp
                         (concat "\\("
                                 (if (string-match
                                      "^[^\0-\177]\\{1,6\\}" string)
                                     (setq string
                                           (substring string (match-end 0)))
                                   string)
                                 "\\)"
                                 (if (string-match
                                      "[^\0-\177]\\{1,6\\}$" after-string)
                                     (setq after-string
                                           (substring after-string
                                                      0 (match-beginning 0)))))
                         (line-end-position) t)
                        (goto-char (match-end 1)))
                       (t (search-forward-regexp
                           string
                           (line-end-position) t))))
                    (setq lline line
                          lbuffer (current-buffer))
                    (if box
                        (progn
                          (if (and lstart (= snippet lsnippet))
                              (setq close-data
                                    (nconc
                                     (preview-place-preview
                                      snippet
                                      (save-excursion
                                        (preview-back-command
                                         (= (prog1 (point)
                                              (goto-char lstart))
                                            lstart))
                                        (point))
                                      (point)
                                      (preview-TeX-bb box)
                                      (cons lcounters counters)
                                      tempdir
                                      (cdr open-data))
                                     close-data))
                            (with-current-buffer run-buffer
                              (preview-log-error
                               (list 'error
                                     (format
                                      "End of Preview snippet %d unexpected"
                                      snippet)) "Parser")))
                          (setq lstart nil))
                      ;; else-part of if box
                      (setq lstart (point) lcounters counters)
                      ;; >= because snippets in between might have
                      ;; been ignored because of TeX-default-extension
                      (unless (>= snippet (1+ lsnippet))
                        (with-current-buffer run-buffer
                          (preview-log-error
                           (list 'error
                                 (format
                                  "Preview snippet %d out of sequence"
                                  snippet)) "Parser"))))))))
          (preview-call-hook 'close (car open-data) close-data))))))

(defun preview-get-dpi ()
  ;; TODO: Remove false-case when required emacs version is bumped to
  ;; 24.4 or newer as this is the version where
  ;; `frame-monitor-attributes' has been introduced.
  (if (fboundp 'frame-monitor-attributes)
      (let* ((monitor-attrs (frame-monitor-attributes))
             (mm-dims (cdr (assoc 'mm-size monitor-attrs)))
             (mm-width (nth 0 mm-dims))
             (mm-height (nth 1 mm-dims))
             (pixel-dims (cdddr (assoc 'geometry monitor-attrs)))
             (pixel-width (nth 0 pixel-dims))
             (pixel-height (nth 1 pixel-dims)))
        (cons (/ (* 25.4 pixel-width) mm-width)
              (/ (* 25.4 pixel-height) mm-height)))
    (cons (/ (* 25.4 (display-pixel-width))
             (display-mm-width))
          (/ (* 25.4 (display-pixel-height))
             (display-mm-height)))))

(defun preview-get-geometry ()
  "Transfer display geometry parameters from current display.
Returns list of scale, resolution and colors.  Calculation
is done in current buffer."
  (condition-case err
      (let* ((geometry
              (list (preview-hook-enquiry preview-scale-function)
                    (preview-get-dpi)
                    (preview-get-colors)))
             (preview-min-spec
              (* (cdr (nth 1 geometry))
                 (/
                  (preview-inherited-face-attribute
                   'preview-reference-face :height 'default)
                  720.0))))
        (setq preview-icon (preview-make-image 'preview-icon-specs)
              preview-error-icon (preview-make-image
                                  'preview-error-icon-specs)
              preview-nonready-icon (preview-make-image
                                     'preview-nonready-icon-specs))
        geometry)
    (error (error "Display geometry unavailable: %s"
                  (error-message-string err)))))

(defun preview-set-geometry (geometry)
  "Set geometry variables from GEOMETRY.
Buffer-local `preview-scale', `preview-resolution',
and `preview-colors' are set as given."
  (setq preview-scale (nth 0 geometry)
        preview-resolution (nth 1 geometry)
        preview-colors (nth 2 geometry)))

(defun preview-get-colors ()
  "Return colors from the current display.
Fetches the current screen colors and makes a vector
of colors as numbers in the range 0..65535.
Pure borderless black-on-white will return triple NIL.
The fourth value is the transparent border thickness."
  (let
      ((bg (color-values (preview-inherited-face-attribute
                          'preview-reference-face :background 'default)))
       (fg (color-values (preview-inherited-face-attribute
                          'preview-reference-face :foreground 'default)))
       (mask (preview-get-heuristic-mask)))
    (if (equal '(65535 65535 65535) bg)
        (setq bg nil))
    (if (equal '(0 0 0) fg)
        (setq fg nil))
    (unless (and (numberp preview-transparent-border)
                 (consp mask) (integerp (car mask)))
      (setq mask nil))
    (vector bg fg mask preview-transparent-border)))

(defun preview-start-dvipng ()
  "Start a DviPNG process.."
  (let* (;; (file preview-gs-file)
         tempdir
         (res (/ (* (car preview-resolution)
                    (preview-hook-enquiry preview-scale))
                 (preview-get-magnification)))
         (resolution  (format " -D%d " res))
         (colors (preview-dvipng-color-string preview-colors res))
         (command (with-current-buffer TeX-command-buffer
                    (prog1
                        (concat (TeX-command-expand preview-dvipng-command)
                                " " colors resolution)
                      (setq tempdir TeX-active-tempdir))))
         (name "Preview-DviPNG"))
    (setq TeX-active-tempdir tempdir)
    (goto-char (point-max))
    (insert-before-markers "Running `" name "' with ``" command "''\n")
    (setq mode-name name)
    (setq TeX-sentinel-function
          (lambda (_process name) (message "%s: done." name)))
    (if TeX-process-asynchronous
        (let ((process (start-process name (current-buffer) TeX-shell
                                      TeX-shell-command-option
                                      command)))
          (if TeX-after-start-process-function
              (funcall TeX-after-start-process-function process))
          (TeX-command-mode-line process)
          (set-process-filter process #'TeX-command-filter)
          (set-process-sentinel process #'TeX-command-sentinel)
          (set-marker (process-mark process) (point-max))
          (push process compilation-in-progress)
          (sit-for 0)
          process)
      (setq mode-line-process ": run")
      (force-mode-line-update)
      (call-process TeX-shell nil (current-buffer) nil
                    TeX-shell-command-option
                    command))))

(defun preview-start-dvips (&optional fast)
  "Start a DviPS process.
If FAST is set, do a fast conversion."
  (let* (;; (file preview-gs-file)
         tempdir
         (command (with-current-buffer TeX-command-buffer
                    (prog1
                        (TeX-command-expand (if fast
                                                preview-fast-dvips-command
                                              preview-dvips-command))
                      (setq tempdir TeX-active-tempdir))))
         (name "Preview-DviPS"))
    (setq TeX-active-tempdir tempdir)
    (setq preview-ps-file (and fast
                               (preview-make-filename
                                (preview-make-filename
                                 "preview.ps" tempdir)
                                tempdir)))
    (goto-char (point-max))
    (insert-before-markers "Running `" name "' with ``" command "''\n")
    (setq mode-name name)
    (setq TeX-sentinel-function
          (lambda (_process name) (message "%s: done." name)))
    (if TeX-process-asynchronous
        (let ((process (start-process name (current-buffer) TeX-shell
                                      TeX-shell-command-option
                                      command)))
          (if TeX-after-start-process-function
              (funcall TeX-after-start-process-function process))
          (TeX-command-mode-line process)
          (set-process-filter process #'TeX-command-filter)
          (set-process-sentinel process #'TeX-command-sentinel)
          (set-marker (process-mark process) (point-max))
          (push process compilation-in-progress)
          (sit-for 0)
          process)
      (setq mode-line-process ": run")
      (force-mode-line-update)
      (call-process TeX-shell nil (current-buffer) nil
                    TeX-shell-command-option
                    command))))

(defun preview-start-pdf2dsc ()
  "Start a PDF2DSC process."
  (let* ((file preview-gs-file)
         tempdir
         pdfsource
         (command (with-current-buffer TeX-command-buffer
                    (prog1
                        (TeX-command-expand preview-pdf2dsc-command)
                      (setq tempdir TeX-active-tempdir
                            pdfsource (funcall (car file) "pdf" t)))))
         (name "Preview-PDF2DSC"))
    (setq TeX-active-tempdir tempdir)
    (setq preview-ps-file (preview-attach-filename
                           pdfsource
                           (preview-make-filename
                            (preview-make-filename
                             "preview.dsc" tempdir)
                            tempdir)))
    (goto-char (point-max))
    (insert-before-markers "Running `" name "' with ``" command "''\n")
    (setq mode-name name)
    (setq TeX-sentinel-function
          (lambda (_process name) (message "%s: done." name)))
    (if TeX-process-asynchronous
        (let ((process (start-process name (current-buffer) TeX-shell
                                      TeX-shell-command-option
                                      command)))
          (if TeX-after-start-process-function
              (funcall TeX-after-start-process-function process))
          (TeX-command-mode-line process)
          (set-process-filter process #'TeX-command-filter)
          (set-process-sentinel process #'TeX-command-sentinel)
          (set-marker (process-mark process) (point-max))
          (push process compilation-in-progress)
          (sit-for 0)
          process)
      (setq mode-line-process ": run")
      (force-mode-line-update)
      (call-process TeX-shell nil (current-buffer) nil
                    TeX-shell-command-option
                    command))))

(defun preview-TeX-inline-sentinel (process _name)
  "Sentinel function for preview.
See `TeX-sentinel-function' and `set-process-sentinel'
for definition of PROCESS and NAME."
  (if process (TeX-format-mode-line process))
  (let ((status (process-status process)))
    (if (memq status '(signal exit))
        (delete-process process))
    (when (eq status 'exit)
      (save-excursion
        (goto-char (point-max))
        (forward-line -1)
        (if (search-forward "abnormally with code 1" nil t)
            (replace-match "as expected with code 1" t t)
          (if (search-forward "finished" nil t)
              (insert " with nothing to show"))))
      (condition-case err
          (preview-call-hook 'open)
        (error (preview-log-error err "LaTeX" process)))
      (preview-reraise-error process))))

(defcustom preview-format-extensions '(".fmt" ".efmt")
  "Possible extensions for format files.
Those are just needed for cleanup."
  :group 'preview-latex
  :type '(repeat string))

(defun preview-format-kill (format-cons)
  "Kill a cached format.
FORMAT-CONS is intended to be an element of `preview-dumped-alist'.
Tries through `preview-format-extensions'."
  (dolist (ext preview-format-extensions)
    (condition-case nil
        (delete-file (preview-dump-file-name (concat (car format-cons) ext)))
      (file-error nil))))

(defun preview-dump-file-name (file)
  "Make a file name suitable for dumping from FILE."
  (if file
      (concat (file-name-directory file)
              "prv_"
              (progn
                (setq file (file-name-nondirectory file))
                (while (string-match " " file)
                  (setq file (replace-match "_" t t file)))
                file))
    "prv_texput"))

(defun preview-do-replacements (string replacements)
  "Perform replacements in string.
STRING is the input string, REPLACEMENTS is a list of replacements.
A replacement is a cons-cell, where the car is the match string,
and the cdr is a list of strings or symbols.  Symbols get dereferenced,
and strings get evaluated as replacement strings."
  (let (rep case-fold-search)
    (while replacements
      (setq rep (pop replacements))
      (cond ((symbolp rep)
             (setq string (preview-do-replacements
                           string (symbol-value rep))))
            ((string-match (car rep) string)
             (setq string
                   (mapconcat (lambda(x)
                                (if (symbolp x)
                                    (symbol-value x)
                                  (replace-match x t nil string)))
                              (cdr rep) ""))))))
  string)

(defconst preview-LaTeX-disable-pdfoutput
  '(("\\`\\(pdf[^ ]*\\)\
\\(\\( +[-&]\\([^ \"]\\|\"[^\"]*\"\\)*\\|\
 +\"[-&][^\"]*\"\\)*\\)\\(.*\\)\\'"
   . ("\\1\\2 \"\\\\pdfoutput=0 \" \\5")))
  "This replacement places `\"\\pdfoutput=0 \"' after the options
of any command starting with `pdf'.")

(defcustom preview-LaTeX-command-replacements
  nil
  "Replacement for `preview-LaTeX-command'.
This is passed through `preview-do-replacements'."
  :group 'preview-latex
  :type '(repeat
          (choice
           (symbol :tag "Named replacement" :value preview-LaTeX-disable-pdfoutput)
           (cons (string :tag "Matched string")
                 (repeat :tag "Concatenated elements for replacement"
                         (choice (symbol :tag "Variable with literal string")
                                 (string :tag "non-literal regexp replacement")))))))

(defvar preview-format-name nil
  "Format name when enabling preamble cache.")

(defcustom preview-dump-replacements
  '(preview-LaTeX-command-replacements
  ;; If -kanji option exists, pick it up as the second match.
  ;; Discard all other options.
    ("\\`\\([^ ]+\\)\
\\(?: +\\(?:\\(--?kanji[= ][^ ]+\\)\\|\\(--?output-directory[= ][^ ]+\\)\\|-\\(?:[^ \\\"]\\|\\\\.\\|\"[^\"]*\"\\)*\\)\\)*\\(.*\\)\\'"
     . ("\\1 -ini \\2 \\3 -interaction=nonstopmode \"&\\1\" " preview-format-name ".ini \\4")))
  "Generate a dump command from the usual preview command."
  :group 'preview-latex
  :type '(repeat
          (choice (symbol :tag "Named replacement")
                  (cons string (repeat (choice symbol string))))))

(defcustom preview-undump-replacements
  ;; If -kanji option exists, pick it up as the second match.
  ;; Discard all other options.
  '(("\\`\\([^ ]+\\)\
\\(?: +\\(?:\\(--?kanji[= ][^ ]+\\)\\|\\(--?output-directory[= ][^ ]+\\)\\|-\\(?:[^ \\\"]\\|\\\\.\\|\"[^\"]*\"\\)*\\)\\)*.*\
 \"\\\\input\" \"\\\\detokenize{\" \\(.*\\) \"}\"\\'"
     . ("\\1 \\2 \\3 -interaction=nonstopmode -file-line-error "
        preview-format-name " \"/AUCTEXINPUT{\" \\4 \"}\"")))
  ;; See the ini file code below in `preview-cache-preamble' for the
  ;; weird /AUCTEXINPUT construct.  In short, it is crafted so that
  ;; dumped format file can read file of non-ascii name.
  "Use a dumped format for reading preamble."
  :group 'preview-latex
  :type '(repeat
          (choice (symbol :tag "Named replacement")
                  (cons string (repeat (choice symbol string))))))


(defun preview-cache-preamble (&optional format-cons)
  "Dump a pregenerated format file.
For the rest of the session, this file is used when running
on the same master file.

Returns the process for dumping, nil if there is still a valid
format available.

If FORMAT-CONS is non-nil, a previous format may get reused."
  (interactive)
  (setq TeX-current-process-region-p nil)
  (let* ((dump-file
          (expand-file-name (preview-dump-file-name (TeX-master-file "ini"))))
         (master (TeX-master-file))
         (format-name (expand-file-name master))
         (preview-format-name (shell-quote-argument
                               (preview-dump-file-name (file-name-nondirectory
                                                        master))))
         (master-file (expand-file-name (TeX-master-file t)))
         (command (preview-do-replacements
                   (TeX-command-expand
                    (preview-string-expand preview-LaTeX-command))
                   preview-dump-replacements))
         (preview-auto-cache-preamble nil))
    (unless (and (consp (cdr format-cons))
                 (string= command (cadr format-cons)))
      (unless format-cons
        (setq format-cons (assoc format-name preview-dumped-alist)))
      (if format-cons
          (preview-cache-preamble-off format-cons)
        (setq format-cons (list format-name))
        (push format-cons preview-dumped-alist))
      ;; mylatex.ltx expects a file name to follow.  Bad. `.tex'
      ;; in the tools bundle is an empty file.
      (write-region "\\let\\PREVIEWdump\\dump\\def\\dump{%
\\edef\\next{{\\ifx\\pdfoutput\\undefined\\else\
\\pdfoutput=\\the\\pdfoutput\\relax\\fi\
\\the\\everyjob}}\\everyjob\\next\\catcode`\\ 10 %
\\catcode`/ 0 %
\\def\\AUCTEXINPUT##1{\\catcode`/ 12\\relax\\catcode`\\ 9\\relax\\input\\detokenize{##1}\\relax}%
\\let\\dump\\PREVIEWdump\\dump}\\input mylatex.ltx \\relax%\n" nil dump-file)
      (TeX-save-document #'TeX-master-file)
      (prog1 (preview-generate-preview master command)
        (add-hook 'kill-emacs-hook #'preview-cleanout-tempfiles t)
        (setq TeX-sentinel-function
              (lambda (process _status)
                (condition-case err
                    (progn
                      (if (and (eq (process-status process) 'exit)
                               (zerop (process-exit-status process)))
                          (preview-watch-preamble
                           master-file
                           command
                           format-cons)
                        (preview-format-kill format-cons))
                      (delete-file dump-file))
                  (error (preview-log-error err "Dumping" process)))
                (preview-reraise-error process)))))))

(defun preview-cache-preamble-off (&optional old-format)
  "Clear the pregenerated format file.
The use of the format file is discontinued.
OLD-FORMAT may already contain a format-cons as
stored in `preview-dumped-alist'."
  (interactive)
  (unless old-format
    (setq old-format
          (let ((master-file (expand-file-name (TeX-master-file))))
            (or (assoc master-file preview-dumped-alist)
                (car (push (list master-file) preview-dumped-alist))))))
  (preview-unwatch-preamble old-format)
  (preview-format-kill old-format)
  (setcdr old-format nil))

(defun preview-region (begin end)
  "Run preview on region between BEGIN and END."
  (interactive "r")
  (let ((TeX-region-extra
         ;; Write out counter information to region.
         (concat (preview--counter-information begin)
                 TeX-region-extra)))
    (TeX-region-create (TeX-region-file TeX-default-extension)
                       (buffer-substring-no-properties begin end)
                       (if buffer-file-name
                           (file-name-nondirectory buffer-file-name)
                         "<none>")
                       (TeX-current-offset begin)))
  (setq TeX-current-process-region-p t)
  (preview-generate-preview (TeX-region-file)
                            (preview-do-replacements
                             (TeX-command-expand
                              (preview-string-expand preview-LaTeX-command))
                             preview-LaTeX-command-replacements)))

(defun preview-buffer ()
  "Run preview on current buffer."
  (interactive)
  (preview-region (point-min) (point-max)))

;; We have a big problem: When we are dumping preambles, diagnostics
;; issued in later runs will not make it to the output when the
;; predumped format skips the preamble.  So we have to place those
;; after \begin{document}.  This we can only do if regions never
;; include the preamble.  We could do this in our own functions, but
;; that would not extend to the operation of C-c C-r g RET.  So we
;; make this preamble skipping business part of TeX-region-create.
;; This will fail if the region is to contain just part of the
;; preamble -- a bad idea anyhow.

(defun preview--skip-preamble-region (region-text region-offset)
  "Skip preamble for the sake of predumped formats.
Helper function of `TeX-region-create'.

If REGION-TEXT doesn't contain preamble, it returns nil.
Otherwise, it returns cons (ALTERED-TEXT . ALTERED-OFFSET) where
ALTERED-TEXT is REGION-TEXT without the preamble part and
ALTERED-OFFSET is REGION-OFFSET increased by the number of lines
of the preamble part of REGION-TEXT."
  (if (string-match TeX-header-end region-text)
      (cons (substring region-text (match-end 0))
            (with-temp-buffer
              (insert (substring region-text 0 (match-end 0)))
              (+ region-offset (TeX-current-offset))))))

(defun preview-document ()
  "Run preview on master document."
  (interactive)
  (TeX-save-document #'TeX-master-file)
  (setq TeX-current-process-region-p nil)
  (preview-generate-preview
   (TeX-master-file)
   (preview-do-replacements
    (TeX-command-expand
     (preview-string-expand preview-LaTeX-command))
    preview-LaTeX-command-replacements)))

(defun preview-environment (count)
  "Run preview on LaTeX environment.
This avoids running environments through preview that are
indicated in `preview-inner-environments'.  If you use a prefix
argument COUNT, the corresponding level of outward nested
environments is selected."
  (interactive "p")
  (save-excursion
    (let (currenv)
      (dotimes (_ (1- count))
        (setq currenv (LaTeX-current-environment))
        (if (string= currenv "document")
            (error "No enclosing outer environment found"))
        (LaTeX-find-matching-begin))
      (while (member (setq currenv (LaTeX-current-environment))
                     preview-inner-environments)
        (LaTeX-find-matching-begin))
      (if (string= currenv "document")
          (error "No enclosing outer environment found"))
      (preview-region
       (save-excursion (LaTeX-find-matching-begin) (point))
       (save-excursion (LaTeX-find-matching-end) (point))))))

(defun preview-section ()
  "Run preview on LaTeX section." (interactive)
  (save-excursion
    (LaTeX-mark-section)
    (preview-region (region-beginning) (region-end))))


(defun preview-generate-preview (file command)
  "Generate a preview.
FILE the file (without default extension), COMMAND is the command
to use.

It returns the started process."
  (let* ((geometry (preview-get-geometry))
         (commandbuff (current-buffer))
         (pr-file (cons
                   #'TeX-active-master
                   (file-name-nondirectory file)))
         (master (TeX-master-file))
         (master-file (expand-file-name master))
         (dumped-cons (assoc master-file
                             preview-dumped-alist))
         process)
    (unless dumped-cons
      (push (setq dumped-cons (cons master-file
                                    (if (eq preview-auto-cache-preamble 'ask)
                                        (y-or-n-p "Cache preamble? ")
                                      preview-auto-cache-preamble)))
            preview-dumped-alist))
    (when (cdr dumped-cons)
      (let* (TeX-current-process-region-p)
        (setq process (preview-cache-preamble dumped-cons))
        (if process
            ;; FIXME: Use `add-function'.
            (setq TeX-sentinel-function
                  (let ((prev-fun TeX-sentinel-function))
                    (lambda (process string)
                      (funcall prev-fun process string)
                      (TeX-inline-preview-internal
                       command file
                       pr-file commandbuff
                       dumped-cons
                       master
                       geometry
                       (buffer-string))))))))
    (or process
        (TeX-inline-preview-internal command file
                                     pr-file commandbuff
                                     dumped-cons master
                                     geometry))))

(defun TeX-inline-preview-internal (command file pr-file
                                    commandbuff dumped-cons _master
                                    geometry
                                    &optional str)
  "Internal stuff for previewing.
COMMAND and FILE should be explained in `TeX-command-list'.
PR-FILE is the target file name in the form for `preview-gs-file'.
COMMANDBUFF, DUMPED-CONS, MASTER, and GEOMETRY are
internal parameters, STR may be a log to insert into the current log."
  (set-buffer commandbuff)
  (let*
      ((preview-format-name (shell-quote-argument
                             (concat "&"
                                     (preview-dump-file-name
                                      ;; Get the filename from
                                      ;; `TeX-master-file' with prv to
                                      ;; get the correct path but then
                                      ;; strip the extension
                                      (file-name-sans-extension
                                       (TeX-master-file "prv" t))))))
       (process-environment (copy-sequence process-environment))
       (process
        (progn
          ;; Fix Bug#20773, Bug#27088.
          ;; Make LaTeX not to insert newline in lines necessary to
          ;; identify Bounding Boxes.
          (setenv "max_print_line" "1000")
          (TeX-run-command
           "Preview-LaTeX"
           (if (consp (cdr dumped-cons))
               (preview-do-replacements
                command
                preview-undump-replacements)
             command)
           file))))
    (condition-case err
        (progn
          (when str
            (save-excursion
              (goto-char (point-min))
              (insert str)
              (when (= (process-mark process) (point-min))
                (set-marker (process-mark process) (point)))))
          (preview-set-geometry geometry)
          (setq preview-gs-file pr-file)
          (setq TeX-sentinel-function #'preview-TeX-inline-sentinel)
          (TeX-parse-reset)
          (setq TeX-parse-function #'TeX-parse-TeX)
          (if TeX-process-asynchronous
              process
            (TeX-synchronous-sentinel "Preview-LaTeX" file process)))
      (error (preview-log-error err "Preview" process)
             (delete-process process)
             (preview-reraise-error process)))))

(defconst preview-version "13.1"
  "Preview version.
If not a regular release, the date of the last change.")

(defconst preview-release-date "2022-02-20"
  "Preview release date using the ISO 8601 format, yyyy-mm-dd.")

(defun preview-dump-state (buffer)
  (condition-case nil
      (progn
        (unless (local-variable-p 'TeX-command-buffer (current-buffer))
          (setq buffer (with-current-buffer buffer (TeX-active-buffer))))
        (when (bufferp buffer)
          (insert "\nRun buffer contents:\n\n")
          (if (< (buffer-size buffer) 5000)
              (insert-buffer-substring buffer)
            (insert-buffer-substring buffer 1 2500)
            (insert "...\n\n[...]\n\n\t...")
            (insert-buffer-substring buffer
                                     (- (buffer-size buffer) 2500)
                                     (buffer-size buffer)))
          (insert "\n")))
    (error nil)))

;;;###autoload
(defun preview-report-bug () "Report a bug in the preview-latex package."
       (interactive)
       (let ((reporter-prompt-for-summary-p "Bug report subject: "))
         (reporter-submit-bug-report
          "bug-auctex@gnu.org"
          preview-version
          '(AUCTeX-version
            LaTeX-command-style
            image-types
            preview-image-type
            preview-image-creators
            preview-dvipng-image-type
            preview-dvipng-command
            preview-pdf2dsc-command
            preview-gs-command
            preview-gs-options
            preview-gs-image-type-alist
            preview-fast-conversion
            preview-prefer-TeX-bb
            preview-dvips-command
            preview-fast-dvips-command
            preview-scale-function
            preview-LaTeX-command
            preview-required-option-list
            preview-preserve-counters
            preview-default-option-list
            preview-default-preamble
            preview-LaTeX-command-replacements
            preview-dump-replacements
            preview-undump-replacements
            preview-auto-cache-preamble
            preview-TeX-style-dir)
          (let ((buf (current-buffer)))
            (lambda () (preview-dump-state buf)))
          (lambda ()
            (insert (format "\nOutput from running `%s -h':\n"
                            preview-gs-command))
            (call-process preview-gs-command nil t nil "-h")
            (insert "\n"))
          "Remember to cover the basics.  Including a minimal LaTeX example
file exhibiting the problem might help."
          )))

(provide 'preview)
;;; preview.el ends here
