dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

commit 4455a4eda08dd6e84b0159dd4ab2cde4fb75fbdb
parent a08a9ce6307ea87cb75a14353e26a9e8edecbcf8
Author: Lukas Henkel <lh@entf.net>
Date:   Mon, 13 Dec 2021 22:15:21 +0100

Add pdf-tools

Diffstat:
Aelpa/pdf-tools-20211110.513/README | 388+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/build/Makefile | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/build/server/.gitignore | 25+++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/build/server/Makefile.am | 44++++++++++++++++++++++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/build/server/autobuild | 534+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/build/server/autogen.sh | 5+++++
Aelpa/pdf-tools-20211110.513/build/server/configure.ac | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/build/server/epdfinfo.c | 3718+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/build/server/epdfinfo.h | 252+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/build/server/install_test.cpp | 7+++++++
Aelpa/pdf-tools-20211110.513/build/server/m4/ax_check_compile_flag.m4 | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/build/server/poppler-hack.cc | 122+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/build/server/poppler-versions | 12++++++++++++
Aelpa/pdf-tools-20211110.513/build/server/synctex_parser.c | 8924+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/build/server/synctex_parser.h | 429+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/build/server/synctex_parser_advanced.h | 554+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/build/server/synctex_parser_local.h | 3+++
Aelpa/pdf-tools-20211110.513/build/server/synctex_parser_readme.txt | 204+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/build/server/synctex_parser_utils.c | 570+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/build/server/synctex_parser_utils.h | 163+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/build/server/synctex_parser_version.txt | 1+
Aelpa/pdf-tools-20211110.513/build/server/synctex_version.h | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/build/server/test/docker/.gitignore | 2++
Aelpa/pdf-tools-20211110.513/build/server/test/docker/lib/run-tests | 9+++++++++
Aelpa/pdf-tools-20211110.513/build/server/test/docker/lib/yes-or-enter | 9+++++++++
Aelpa/pdf-tools-20211110.513/build/server/test/docker/templates/Dockerfile.in | 4++++
Aelpa/pdf-tools-20211110.513/build/server/test/docker/templates/arch.Dockerfile.in | 4++++
Aelpa/pdf-tools-20211110.513/build/server/test/docker/templates/centos-7.Dockerfile.in | 3+++
Aelpa/pdf-tools-20211110.513/build/server/test/docker/templates/debian-10.Dockerfile.in | 3+++
Aelpa/pdf-tools-20211110.513/build/server/test/docker/templates/debian-8.Dockerfile.in | 4++++
Aelpa/pdf-tools-20211110.513/build/server/test/docker/templates/debian-9.Dockerfile.in | 4++++
Aelpa/pdf-tools-20211110.513/build/server/test/docker/templates/fedora-32.Dockerfile.in | 3+++
Aelpa/pdf-tools-20211110.513/build/server/test/docker/templates/fedora-33.Dockerfile.in | 3+++
Aelpa/pdf-tools-20211110.513/build/server/test/docker/templates/fedora-34.Dockerfile.in | 3+++
Aelpa/pdf-tools-20211110.513/build/server/test/docker/templates/fedora-35.Dockerfile.in | 3+++
Aelpa/pdf-tools-20211110.513/build/server/test/docker/templates/gentoo.Dockerfile.in | 5+++++
Aelpa/pdf-tools-20211110.513/build/server/test/docker/templates/ubuntu-14.Dockerfile.in | 4++++
Aelpa/pdf-tools-20211110.513/build/server/test/docker/templates/ubuntu-16.Dockerfile.in | 4++++
Aelpa/pdf-tools-20211110.513/build/server/test/docker/templates/ubuntu-18.Dockerfile.in | 3+++
Aelpa/pdf-tools-20211110.513/build/server/test/docker/templates/ubuntu-20.10.Dockerfile.in | 3+++
Aelpa/pdf-tools-20211110.513/build/server/test/docker/templates/ubuntu-20.Dockerfile.in | 3+++
Aelpa/pdf-tools-20211110.513/build/server/test/docker/templates/ubuntu-21.Dockerfile.in | 3+++
Aelpa/pdf-tools-20211110.513/epdfinfo | 0
Aelpa/pdf-tools-20211110.513/pdf-annot.el | 1791+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/pdf-annot.elc | 0
Aelpa/pdf-tools-20211110.513/pdf-cache.el | 458+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/pdf-cache.elc | 0
Aelpa/pdf-tools-20211110.513/pdf-dev.el | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/pdf-dev.elc | 0
Aelpa/pdf-tools-20211110.513/pdf-history.el | 170+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/pdf-history.elc | 0
Aelpa/pdf-tools-20211110.513/pdf-info.el | 1744+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/pdf-info.elc | 0
Aelpa/pdf-tools-20211110.513/pdf-isearch.el | 832+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/pdf-isearch.elc | 0
Aelpa/pdf-tools-20211110.513/pdf-links.el | 379+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/pdf-links.elc | 0
Aelpa/pdf-tools-20211110.513/pdf-loader.el | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/pdf-loader.elc | 0
Aelpa/pdf-tools-20211110.513/pdf-macs.el | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/pdf-macs.elc | 0
Aelpa/pdf-tools-20211110.513/pdf-misc.el | 295+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/pdf-misc.elc | 0
Aelpa/pdf-tools-20211110.513/pdf-occur.el | 826+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/pdf-occur.elc | 0
Aelpa/pdf-tools-20211110.513/pdf-outline.el | 606+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/pdf-outline.elc | 0
Aelpa/pdf-tools-20211110.513/pdf-sync.el | 780+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/pdf-sync.elc | 0
Aelpa/pdf-tools-20211110.513/pdf-tools-autoloads.el | 502+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/pdf-tools-pkg.el | 14++++++++++++++
Aelpa/pdf-tools-20211110.513/pdf-tools.el | 529+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/pdf-tools.elc | 0
Aelpa/pdf-tools-20211110.513/pdf-util.el | 1360+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/pdf-util.elc | 0
Aelpa/pdf-tools-20211110.513/pdf-view.el | 1684+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/pdf-view.elc | 0
Aelpa/pdf-tools-20211110.513/pdf-virtual.el | 1038+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/pdf-tools-20211110.513/pdf-virtual.elc | 0
Aelpa/tablist-20200427.2205/tablist-autoloads.el | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/tablist-20200427.2205/tablist-filter.el | 464+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/tablist-20200427.2205/tablist-filter.elc | 0
Aelpa/tablist-20200427.2205/tablist-pkg.el | 11+++++++++++
Aelpa/tablist-20200427.2205/tablist.el | 1945+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/tablist-20200427.2205/tablist.elc | 0
Minit.el | 2+-
86 files changed, 32073 insertions(+), 1 deletion(-)

diff --git a/elpa/pdf-tools-20211110.513/README b/elpa/pdf-tools-20211110.513/README @@ -0,0 +1,388 @@ +#+TITLE: PDF Tools README +#+AUTHOR: Andreas Politz +#+EMAIL: politza@fh-trier.de +#+Maintainer: Vedang Manerikar +#+Maintainer_Email: vedang.manerikar@gmail.com + +[[https://app.circleci.com/pipelines/github/vedang/pdf-tools][https://circleci.com/gh/vedang/pdf-tools.svg?style=svg]] +[[https://stable.melpa.org/#/pdf-tools][http://stable.melpa.org/packages/pdf-tools-badge.svg]] +[[https://melpa.org/#/pdf-tools][http://melpa.org/packages/pdf-tools-badge.svg]] [[https://ci.appveyor.com/project/vedang/pdf-tools][https://ci.appveyor.com/api/projects/status/yqic2san0wi7o5v8/branch/master?svg=true]] + +** About this package + PDF Tools is, among other things, a replacement of DocView for PDF + files. The key difference is that pages are not pre-rendered by + e.g. ghostscript and stored in the file-system, but rather created + on-demand and stored in memory. + + This rendering is performed by a special library named, for + whatever reason, poppler, running inside a server program. This + program is called ~epdfinfo~ and its job is to successively + read requests from Emacs and produce the proper results, i.e. the + PNG image of a PDF page. + + Actually, displaying PDF files is just one part of PDF Tools. + Since poppler can provide us with all kinds of information about a + document and is also able to modify it, there is a lot more we can + do with it. [[http://www.dailymotion.com/video/x2bc1is_pdf-tools-tourdeforce_tech?forcedQuality%3Dhd720][Watch]] + + Please read also about [[#known-problems][known problems.]] + +** Features + + View :: View PDF documents in a buffer with DocView-like + bindings. + + Isearch :: Interactively search PDF documents like any other + buffer, either for a string or a PCRE. + + Occur :: List lines matching a string or regexp in one or more + PDF documents. + + Follow :: + Click on highlighted links, moving to some part of a different + page, some external file, a website or any other URI. Links may + also be followed by keyboard commands. + + Annotations :: Display and list text and markup annotations (like + underline), edit their contents and attributes + (e.g. color), move them around, delete them or + create new ones and then save the modifications + back to the PDF file. + + Attachments :: Save files attached to the PDF-file or list them + in a dired buffer. + + Outline :: Use imenu or a special buffer to examine and navigate + the PDF's outline. + + SyncTeX :: Jump from a position on a page directly to the TeX + source and vice versa. + + Virtual :: + Use a collection of documents as if it were one, big single PDF. + + + Misc :: + - Display PDF's metadata. + - Mark a region and kill the text from the PDF. + - Keep track of visited pages via a history. + - Apply a color filter for reading in low light conditions. + +** Installation + The package may be installed via MELPA and it will try to build the + server part when it is activated the first time. Though the next + section regarding build-prerequisites is still relevant, the rest + of the installation instructions assume a build from within a git + repository. (The MELPA package has a different directory + structure.) + +*** Server prerequisites + You'll need GNU Emacs \ge 24.3 and some form of a GNU/Linux OS. + Other operating systems are currently not supported (patches + welcome). The following instructions assume a Debian-based + system. (The prerequisites may be installed automatically on this + kind of systems, see [[#compilation][Compilation]] .) + + First make sure a suitable build-system is installed. We need at + least a C/C++ compiler (both ~gcc~ and ~g++~), ~make~, ~automake~ + and ~autoconf~. + + Next we need to install a few libraries PDF Tools depends on, some + of which are probably already on your system. +#+begin_src sh + $ sudo aptitude install libpng-dev zlib1g-dev + $ sudo aptitude install libpoppler-glib-dev + $ sudo aptitude install libpoppler-private-dev +#+end_src + On some older Ubuntu systems, the final command will possibly give + an error. This should be no problem, since in some versions this + package was contained in the main package ~libpoppler-dev~. Also + note, that ~zlib1g-dev~ was for a long time called ~libz-dev~, + which it still may be on your system. + + Debian wheezy comes with libpoppler version 0.18, which is pretty + old. The minimally required version is 0.16, but some features of + PDF Tools depend on a more recent version of this library. See + the following table for what they are and what version they + require. + + | You want to ... | Required version | + |-------------------------------------------+------------------| + | ... create and modify text annotations. | \ge 0.19.4 | + | ... search case-sensitive. | \ge 0.22 | + | ... create and modify markup annotations. | \ge 0.26 | + |-------------------------------------------+------------------| + + In case you decide to install libpoppler from source, make sure + to run its configure script with the ~--enable-xpdf-headers~ + option. + + Finally there is one feature (following links of a PDF document by + plain keystrokes) which requires imagemagick's convert utility. + This requirement is optional and you may install it like so: +#+begin_src sh + $ sudo aptitude install imagemagick +#+end_src +**** Compiling on macOS + Although macOS is not officially supported, it has been reported + to have been successfully compiled. You will need to install + poppler which you can get with Homebrew via +#+BEGIN_SRC sh + $ brew install poppler automake +#+END_SRC + + You will also have to help ~pkg-config~ find some libraries by + setting ~PKG_CONFIG_PATH~, e.g. +#+BEGIN_SRC sh + $ export PKG_CONFIG_PATH=/usr/local/Cellar/zlib/1.2.8/lib/pkgconfig:/usr/local/lib/pkgconfig:/opt/X11/lib/pkgconfig +#+END_SRC + or likewise within Emacs using `setenv`. + + After that, compilation should proceed as normal. +**** FreeBSD + Although not officially supported, it has been reported that + pdf-tools work well on FreeBSD. Instead of building pdf-tools, you + can install one of the OS packages with, e.g. +#+BEGIN_SRC sh + $ pkg install pdf-tools-emacs26 +#+END_SRC + To see the current list of pdf-tools packages for FreeBSD visit + [[https://repology.org/metapackages/?search=pdf-tools&inrepo=freebsd][the Repology list]]. + + To build pdf-tools from either MELPA or directly from the source + repository, install the dependencies with +#+BEGIN_SRC sh + $ pkg install autotools gmake poppler-glib +#+END_SRC + + If you choose not to install from MELPA, you must substitute + ~gmake~ for ~make~ in the instructions below. +**** Compiling on CentOS + It is possible to compile pdf-tools on CentOS. Install poppler the dependencies with: +#+BEGIN_SRC sh + $ yum install poppler-devel poppler-glib-devel +#+END_SRC + +**** Compiling on Fedora +#+BEGIN_SRC sh + $ sudo dnf install make automake autoconf gcc gcc-c++ ImageMagick libpng-devel zlib-devel poppler-glib-devel +#+END_SRC + +**** Compiling on Alpine Linux +#+BEGIN_SRC sh + $ apk add build-base g++ gcc automake autoconf libpng-dev glib-dev poppler-dev +#+END_SRC + +**** Compiling on Windows + PDF Tools can be built and used on Windows using the MSYS2 + compiler. This will work with native (not cygwin) Windows builds of + emacs. This includes the standard binaries provided by the GNU + project, those available as MSYS2 packages and numerous third-party + binaries. It has been tested with Emacs 25.1. Instructions are + provided under [[#compilation-and-installation-on-windows][Compilation and installation on Windows]], below. + PDF Tools will successfully compile using Cygwin, but it will not be + able to open PDFs properly due to the way binaries compiled with Cygwin + handle file paths. + +*** Compilation + :PROPERTIES: + :CUSTOM_ID: compilation + :END: + Now it's time to compile the source. +#+begin_src sh + $ cd /path/to/pdf-tools + $ make install-server-deps # optional + $ make -s +#+end_src + The ~make install-server-deps~ command will try to install all + necessary programs and libraries to build the package, though + it'll only work, if ~sudo~ and ~apt-get~ are available. + + This should compile the source code and create a Emacs Lisp + Package in the root directory of the project. The configure script + also tells you at the very end, which features, depending on the + libpoppler version, will be available. These commands should give + no error, otherwise you are in trouble. +**** Compilation and installation on Windows + :PROPERTIES: + :CUSTOM_ID: compilation-and-installation-on-windows + :END: + If using the GNU binaries for Windows, support for PNG and zlib + must first be installed by copying the appropriate dlls into + emacs' ~bin/~ directory. Most third-party binaries come with this + already done. + + First, install [[http://www.msys2.org/][install MSYS2]] and update + the package database and core packages using the instructions + provided. Then, to compile PDF tools itself: + + 1. Open msys2 shell + + 2. Update and install dependencies, skipping any you already have + #+BEGIN_SRC sh + $ pacman -Syu + $ pacman -S base-devel + $ pacman -S mingw-w64-x86_64-toolchain + $ pacman -S mingw-w64-x86_64-zlib + $ pacman -S mingw-w64-x86_64-libpng + $ pacman -S mingw-w64-x86_64-poppler + $ pacman -S mingw-w64-x86_64-imagemagick + #+END_SRC + + 3. Install PDF tools in Emacs, but do not try to compile the + server. Instead, get a separate copy of the source somewhere + else. + #+BEGIN_SRC sh + $ git clone https://github.com/politza/pdf-tools + #+END_SRC + + 4. Open ~mingw64~ shell (*Note:* You must use ~mingw64.exe~ and not ~msys2.exe~) + + 5. Compile pdf-tools + #+BEGIN_SRC sh + $ cd /path/to/pdf-tools + $ make -s + #+END_SRC + + 6. This should produce a file ~server/epdfinfo.exe~. Copy this file + into the ~pdf-tools/~ installation directory in your Emacs. + + 7. Start Emacs and activate the package. + #+BEGIN_SRC + M-x pdf-tools-install RET + #+END_SRC + + 8. Test. + #+BEGIN_SRC + M-x pdf-info-check-epdfinfo RET + #+END_SRC + + If this is successful, ~(pdf-tools-install)~ can be added to Emacs' + config. Note that libraries from other GNU utilities, such as Git + for Windows, may interfere with those needed by PDF Tools. + ~pdf-info-check-epdinfo~ will succeed, but errors occur when trying + to view a PDF file. This can be fixed by ensuring that the MSYS + libraries are always preferred in Emacs: + + #+BEGIN_SRC emacs-lisp + (setenv "PATH" (concat "C:\\msys64\\mingw64\\bin;" (getenv "PATH"))) + #+END_SRC + +*** Elisp prerequisites + This package depends on the following Elisp packages, which should + be installed before installing the PDF Tools package. + + | Package | Required version | + |-----------+----------------------------------| + | [[https://elpa.gnu.org/packages/let-alist.html][let-alist]] | >= 1.0.4 (comes with Emacs 25.2) | + | [[http://melpa.org/#/tablist][tablist]] | >= 0.70 | + |-----------+----------------------------------| + +*** Installing + If ~make~ produced the ELP file ~pdf-tools-${VERSION}.tar~ you are + fine. This package contains all the necessary files for Emacs + and may be installed by either using +#+begin_src sh + $ make install-package +#+end_src + or executing the Emacs command +#+begin_src elisp + M-x package-install-file RET pdf-tools-${VERSION}.tar RET +#+end_src + + To complete the installation process, you need to activate the + package by putting +#+begin_src elisp + (pdf-tools-install) +#+end_src + somewhere in your ~.emacs~. Alternatively, and if you care about + start-up time, you may want to use +#+begin_src elisp + (pdf-loader-install) +#+end_src + instead. Next you probably want to take a look at the various + features of what you've just installed. The following two commands + might be of help for doing so. +#+begin_src elisp + M-x pdf-tools-help RET + M-x pdf-tools-customize RET +#+end_src + +*** Updating + Some day you might want to update this package via ~git pull~ and + then reinstall it. Sometimes this may fail, especially if + Lisp-Macros are involved and the version hasn't changed. To avoid + this kind of problems, you should delete the old package via + ~list-packages~, restart Emacs and then reinstall the package. + + This also applies when updating via package and MELPA. + +** Known problems + :PROPERTIES: + :CUSTOM_ID: known-problems + :END: + +*** linum-mode + PDF Tools does not work well together with ~linum-mode~ and + activating it in a ~pdf-view-mode~, e.g. via ~global-linum-mode~, + might make Emacs choke. + +*** auto-revert + Autorevert works by polling the file-system every + ~auto-revert-interval~ seconds, optionally combined with some + event-based reverting via [[https://www.gnu.org/software/emacs/manual/html_node/elisp/File-Notifications.html][file notification]]. But this currently + does not work reliably, such that Emacs may revert the PDF-buffer + while the corresponding file is still being written to (e.g. by + LaTeX), leading to a potential error. + + With a recent [[https://www.gnu.org/software/auctex/][AUCTeX]] installation, you might want to put the + following somewhere in your dotemacs, which will revert the PDF-buffer + *after* the TeX compilation has finished. +#+BEGIN_SRC emacs-lisp + (add-hook 'TeX-after-compilation-finished-functions #'TeX-revert-document-buffer) +#+END_SRC + +*** sublimity + L/R scrolling breaks while zoomed into a pdf, with usage of sublimity smooth scrolling features + +** Some keybindings + +| Navigation | | +|--------------------------------------------+-----------------------| +| Scroll Up / Down by Page-full | ~space~ / ~backspace~ | +| Scroll Up / Down by Line | ~C-n~ / ~C-p~ | +| Scroll Right / Left | ~C-f~ / ~C-b~ | +| Top of Page / Bottom of Page | ~<~ / ~>~ | +| Next Page / Previous Page | ~n~ / ~p~ | +| First Page / Last Page | ~M-<~ / ~M->~ | +| Incremental Search Forward / Backward | ~C-s~ / ~C-r~ | +| Occur (list all lines containing a phrase) | ~M-s o~ | +| Jump to Occur Line | ~RETURN~ | +| Pick a Link and Jump | ~F~ | +| Incremental Search in Links | ~f~ | +| History Back / Forwards | ~B~ / ~N~ | +| Display Outline | ~o~ | +| Jump to Section from Outline | ~RETURN~ | +| Jump to Page | ~M-g g~ | + +| Display | | +|------------------------------------------+-----------------| +| Zoom in / Zoom out | ~+~ / ~-~ | +| Fit Height / Fit Width / Fit Page | ~H~ / ~W~ / ~P~ | +| Trim Margins (set slice to bounding box) | ~s b~ | +| Reset Margins | ~s r~ | +| Reset Zoom | 0 | + +| Annotations | | +|-------------------------------+-------------------------------------------------| +| List Annotations | ~C-c C-a l~ | +| Jump to Annotations from List | ~SPACE~ | +| Mark Annotation for Deletion | ~d~ | +| Delete Marked Annotations | ~x~ | +| Unmark Annotations | ~u~ | +| Close Annotation List | ~q~ | +| Add and Edit Annotations | via Mouse selection and left-click context menu | + +| Syncing with AUCTeX | | +|----------------------------------+-------------| +| Jump to PDF Location from Source | ~C-c C-g~ | +| Jump Source Location from PDF | ~C-mouse-1~ | + +| Miscellaneous | | +|-----------------------------------------------+-----------| +| Refresh File (e.g., after recompiling source) | ~g~ | +| Print File | ~C-c C-p~ | + +# Local Variables: +# mode: org +# End: diff --git a/elpa/pdf-tools-20211110.513/build/Makefile b/elpa/pdf-tools-20211110.513/build/Makefile @@ -0,0 +1,97 @@ +CASK = cask +EMACS ?= emacs +# Handle the mess when inside Emacs. +unexport INSIDE_EMACS #cask not like this. +ifeq ($(EMACS), t) +EMACS = emacs +endif + +emacs = $(EMACS) +emacs_version = $(shell $(emacs) --batch --eval \ + '(princ (format "%s.%s" emacs-major-version emacs-minor-version))') +$(info Using Emacs $(emacs_version)) + +version=$(shell sed -ne 's/^;\+ *Version: *\([0-9.]\)/\1/p' lisp/pdf-tools.el) +pkgname=pdf-tools-$(version) +pkgfile=$(pkgname).tar + +.PHONY: all clean distclean bytecompile test check melpa + +all: $(pkgfile) + +# Create a elpa package including the server +$(pkgfile): .cask/$(emacs_version) server/epdfinfo lisp/*.el + $(CASK) package . + +# Compile the Lisp sources +bytecompile: .cask/$(emacs_version) + $(CASK) exec $(emacs) --batch -L lisp -f batch-byte-compile lisp/*.el + +# Run ERT tests +test: all + PACKAGE_TAR=$(pkgfile) $(CASK) exec ert-runner + +check: test + +# Run the autobuild script tests in docker +test-autobuild: server-test + +# Run all tests +test-all: test test-autobuild + +# Init cask +.cask/$(emacs_version): + $(CASK) install + +# Run the autobuild script (installing depends and compiling) +autobuild: + cd server && ./autobuild + +# Soon to be obsolete targets +melpa-build: autobuild + cp build/epdfinfo . +install-server-deps: ; + +# Create a package like melpa would. +melpa-package: $(pkgfile) + cp $(pkgfile) $(pkgname)-melpa.tar + tar -u --transform='s/server/$(pkgname)\/build\/server/' \ + -f $(pkgname)-melpa.tar \ + $$(git ls-files server) + tar -u --transform='s/Makefile/$(pkgname)\/build\/Makefile/' \ + -f $(pkgname)-melpa.tar \ + Makefile + tar -u --transform='s/README\.org/$(pkgname)\/README/' \ + -f $(pkgname)-melpa.tar \ + README.org + -tar --delete $(pkgname)/epdfinfo \ + -f $(pkgname)-melpa.tar + +# Various clean targets +clean: server-clean + rm -f -- $(pkgfile) + rm -f -- lisp/*.elc + rm -f -- pdf-tools-readme.txt + rm -f -- pdf-tools-$(version).entry + +distclean: clean server-distclean + rm -rf -- .cask + +# Server targets +server/epdfinfo: server/Makefile server/*.[ch] + $(MAKE) -C server + +server/Makefile: server/configure + cd server && ./configure -q + +server/configure: server/configure.ac + cd server && ./autogen.sh + +server-test: server/Makefile + $(MAKE) -C server check + +server-clean: + ! [ -f server/Makefile ] || $(MAKE) -C server clean + +server-distclean: + ! [ -f server/Makefile ] || $(MAKE) -C server distclean diff --git a/elpa/pdf-tools-20211110.513/build/server/.gitignore b/elpa/pdf-tools-20211110.513/build/server/.gitignore @@ -0,0 +1,25 @@ +*.o +.deps/ +Makefile +Makefile.in +aclocal.m4 +autom4te.cache/ +config.h +config.h.in +config.log +config.status +configure +depcomp +epdfinfo +install-sh +libsynctex.a +missing +stamp-h1 +ar-lib +compile +config.h.in~ +.clang_complete +callgrind.out.* +config.guess +config.sub +TAGS diff --git a/elpa/pdf-tools-20211110.513/build/server/Makefile.am b/elpa/pdf-tools-20211110.513/build/server/Makefile.am @@ -0,0 +1,44 @@ +bin_PROGRAMS = epdfinfo +epdfinfo_CFLAGS = -Wall $(glib_CFLAGS) $(poppler_glib_CFLAGS) $(poppler_CFLAGS) \ + $(png_CFLAGS) +epdfinfo_CXXFLAGS = -Wall $(epdfinfo_CFLAGS) +epdfinfo_LDADD = $(glib_LIBS) $(poppler_glib_LIBS) $(poppler_LIBS) \ + $(png_LIBS) libsynctex.a $(zlib_LIBS) +epdfinfo_SOURCES = epdfinfo.c epdfinfo.h poppler-hack.cc + +noinst_LIBRARIES = libsynctex.a +libsynctex_a_SOURCES = synctex_parser.c synctex_parser_utils.c synctex_parser.h \ + synctex_parser_local.h synctex_parser_utils.h +libsynctex_a_CFLAGS = -w $(zlib_CFLAGS) -DSYNCTEX_USE_LOCAL_HEADER + +if HAVE_W32 +epdfinfo_LDADD += -lshlwapi +endif + +SYNCTEX_UPSTREAM = svn://tug.org/texlive/trunk/Build/source/texk/web2c/synctexdir +SYNCTEX_FILES = synctex_parser.c \ + synctex_parser.h \ + synctex_parser_readme.txt \ + synctex_parser_utils.c \ + synctex_parser_utils.h \ + synctex_parser_version.txt \ + synctex_version.h \ + synctex_parser_advanced.h + + +check-local: + @if $(MAKE) --version 2>&1 | grep -q GNU; then \ + cd test && $(MAKE) $(AM_MAKEFLAGS); \ + else \ + echo "Skipping tests in server/test (requires GNU make)"; \ + fi + +synctex-pull: + @if [ -n "$$(git status --porcelain)" ]; then \ + git status; \ + echo "Not checking-out files into a dirty work-directory"; \ + false; \ + fi + for file in $(SYNCTEX_FILES); do \ + svn export --force $(SYNCTEX_UPSTREAM)/$$file; \ + done diff --git a/elpa/pdf-tools-20211110.513/build/server/autobuild b/elpa/pdf-tools-20211110.513/build/server/autobuild @@ -0,0 +1,534 @@ +#!/usr/bin/env sh + +## +## Installs package dependencies and builds the application. +## + +# Don't exit if some command fails. +set +e +# Disable file globbing. +set -f + +# Boolean variables are true if non-empty and false otherwise. + +# Command to install packages. +PKGCMD= +# Args to pass to $PKGCMD. +PKGARGS= +# Required packages. +PACKAGES= +# Whether package installation requires root permissions. +PKG_INSTALL_AS_ROOT=true +# Whether to skip package installation altogether. +PKG_INSTALL_SKIP= +# Whether to force package installation, even if it does not seem +# necessary. +PKG_INSTALL_FORCE= +# Only test if the OS is handled by this script. +DRY_RUN= +# If and where to install the program. +INSTALL_DIR= +# Whether we can install packages. +OS_IS_HANDLED=true +# Which OSs installer to use +OS= + +## +-----------------------------------------------------------+ +## * Utility Functions +## +-----------------------------------------------------------+ + +usage() +{ + cat <<EOF +usage:$(basename "$0") [--help|-n|-i DIR|[-d -D]|[--os OS]] + + -n Don't do anything, but check if this OS is handled. + + -i DIR Install the program in the given directory. + + -d Force dependency installattion. + + -D Skip dependency installattion. + + --os OS Use the given OS's installer + + --help Display this message. + +EOF + exit "$1" +} + +# Search for command $1 in PATH. Print its absolute filename. +which() +{ + if [ -z "$1" ]; then + return 1 + fi + command -v "$1" +} + +# Quote $@ for the shell. +quote() +{ + quoted= + for arg; do + qarg=$(printf "%s" "$arg" | sed -e 's/[|&;<>()$\`"'\'' ]/\\&/g') + if [ -z "$quoted" ]; then + quoted=$qarg + else + quoted="$quoted $qarg" + fi + done + printf "%s" "$quoted" +} + +# Attempt to exec $@ as root. +exec_privileged() { + if [ -z "$1" ]; then + echo "internal error: command is empty" + exit 2 + fi + if [ -w / ]; then + "$@" + elif which sudo >/dev/null 2>&1; then + sudo -- "$@" + retval=$? + sudo -k + return $retval + elif which su >/dev/null 2>&1; then + su -c "$(quote "$@")" + else + echo "No such program: sudo or su" + exit 1 + fi +} + +# Test if $1 is in PATH or exit with a failure status. +assert_program() +{ + if ! which "$1" >/dev/null 2>&1; then + echo "No such program: $1" + exit 1 + fi +} + +# Source filename $1 and echo variable $2. +source_var() +{ + if ! [ -f "$1" ] || ! [ -r "$1" ] || [ -z "$2" ]; then + return 1 + fi + # shellcheck source=/dev/null + . "$1" + eval "printf '%s\n' \$$2" + return 0 +} + +exit_success() +{ + echo "===========================" + echo " Build succeeded. :O) " + echo "===========================" + exit 0 +} + +exit_fail() +{ + echo "===========================" + echo " Build failed. ;o( " + echo "===========================" + if [ -z "$PKG_INSTALL_FORCE" ]; then + echo "Note: maybe try the '-d' option." + fi + exit 1 +} + +# Return 0, if all required packages seem to be installed. +have_packages_installed() +{ + { + which pkg-config || return 1 + if ! [ -f configure ];then + which autoreconf || return 1 + which automake || return 1 + fi + for lib in libpng glib-2.0 poppler poppler-glib zlib; do + pkg-config --exists $lib || return 1 + done + which make || return 1 + which gcc || which cc || return 1 + which g++ || which c++ || return 1 + c++ $(pkg-config --cflags poppler) -o /dev/null -E install_test.cpp 2>/dev/null + [ $? -eq 0 ] || return 1 + return 0 + } >/dev/null 2>&1 +} + +handle_options() +{ + while [ $# -gt 0 ]; do + case $1 in + --help) usage 0;; + -n) DRY_RUN=true;; + -d) PKG_INSTALL_FORCE=true ;; + -D) PKG_INSTALL_SKIP=true ;; + -i) + shift + [ $# -gt 0 ] || usage 1 + if [ "${1%%/}" != "${PWD%%/}" ]; then + INSTALL_DIR=$1 + fi ;; + --os) + shift + [ $# -gt 0 ] || usage 1 + OS="$1" + ;; + *) usage 1 ;; + esac + shift + done + if [ -n "$PKG_INSTALL_SKIP" ] && [ -n "$PKG_INSTALL_FORCE" ]; then + usage 1 + fi +} + +## +-----------------------------------------------------------+ +## * OS Functions +## +-----------------------------------------------------------+ + +# Archlinux +os_arch() { + if ! [ -e "/etc/arch-release" ]; then + return 1; + fi + PKGCMD=pacman + PKGARGS="-S --needed" + PACKAGES="base-devel libpng zlib poppler-glib" + return 0; +} + +# CentOS +os_centos() { + if ! [ -e "/etc/centos-release" ]; then + return 1 + fi + PKGCMD=yum + if yum help install-n >/dev/null 2>&1; then + PKGARGS=install-n + else + PKGARGS=install + fi + PACKAGES="autoconf + automake + gcc + gcc-c++ + libpng-devel + make + pkgconfig + poppler-devel + poppler-glib-devel + zlib-devel" + return 0 +} + +# FreeBSD +os_freebsd() { + if ! which uname >/dev/null 2>&1 || [ "$(uname -s)" != "FreeBSD" ]; then + return 1 + fi + PKGCMD=pkg + PKGARGS=install + PACKAGES="autotools poppler-glib png pkgconf" + return 0 +} + +# OpenBSD +os_openbsd() { + if ! which uname >/dev/null 2>&1 || [ "$(uname -s)" != "OpenBSD" ]; then + return 1 + fi + PKGCMD=pkg_add + PKGARGS="-uU" + PACKAGES="autoconf-2.69p2 automake-1.15.1 poppler poppler-utils png" + export AUTOCONF_VERSION=2.69 + export AUTOMAKE_VERSION=1.15 + if whereis clang++ ;then + export CXX=clang++ + elif whereis eg++ ;then + export CXX=eg++ + else + export CXX=eg++ + PACKAGES="${PACKAGES} g++" + fi + export CXXFLAGS="-std=c++11 -I/usr/local/include/poppler -I/usr/local/include" + return 0 +} + +# Fedora +os_fedora() { + if ! [ -e "/etc/fedora-release" ]; then + return 1 + fi + PKGCMD=dnf + PKGARGS=install + PACKAGES="autoconf + automake + gcc + gcc-c++ + libpng-devel + make + poppler-devel + poppler-glib-devel + zlib-devel" + VERSION=$(source_var /etc/os-release VERSION_ID) + if [ -n "$VERSION" ] && [ "$VERSION" -ge 26 ]; then + PACKAGES="$PACKAGES pkgconf" + else + PACKAGES="$PACKAGES pkgconfig" + fi + return 0 +} + +# Debian/Ubuntu +os_debian() { + if ! [ -e "/etc/debian_version" ]; then + return 1 + fi + PACKAGES="autoconf + automake + g++ + gcc + libpng-dev + libpoppler-dev + libpoppler-glib-dev + libpoppler-private-dev + libz-dev + make + pkg-config" + PKGCMD=apt-get + PKGARGS="install -y" + return 0 +} + +# Msys2 +os_msys2() { + if [ -z "$MSYSTEM" ] || ! [ -r "/etc/profile" ]; then + return 1 + fi + case $MSYSTEM in + MINGW64) + PACKAGES="base-devel + mingw-w64-x86_64-libpng + mingw-w64-x86_64-poppler + mingw-w64-x86_64-toolchain + mingw-w64-x86_64-zlib" ;; + MINGW32) + PACKAGES="base-devel + mingw-w64-i686-libpng + mingw-w64-i686-poppler + mingw-w64-i686-toolchain + mingw-w64-i686-zlib" ;; + MSYS) + case $(uname -m) in + x86_64) + MSYSTEM=MINGW64 ;; + *) + MSYSTEM=MINGW32 ;; + esac + export MSYSTEM + # shellcheck source=/dev/null + . /etc/profile + eval "exec $(quote "$0" "$@")" ;; + *) + echo "Unrecognized MSYSTEM value: $MSYSTEM" + exit 1 ;; + esac + PKGCMD=pacman + PKGARGS="-S --needed" + PKG_INSTALL_AS_ROOT= + return 0 +} + +# MacOS +os_macos() { + if ! which uname >/dev/null 2>&1 || [ "$(uname -s)" != "Darwin" ]; then + return 1 + elif which brew >/dev/null 2>&1; then + PKGCMD=brew + PKGARGS=install + PACKAGES="pkg-config poppler automake" + PKG_INSTALL_AS_ROOT= + # homebrew install libffi as keg-only, meaning we need to set + # PKG_CONFIG_PATH manually so configure can find it + export PKG_CONFIG_PATH="$(brew --prefix libffi)/lib/pkgconfig/" + elif which port >/dev/null 2>&1; then + PKGCMD=port + PKGARGS=install + PACKAGES="pkgconfig poppler automake libpng" + else + return 1 + fi + return 0 +} + +# NixOS +os_nixos() { + # Already in the nix-shell. + if [ -n "$AUTOBUILD_NIX_SHELL" ]; then + return 0 + fi + if ! which nix-shell >/dev/null 2>&1; then + return 1 + fi + if [ -n "$DRY_RUN" ]; then + return 0 + fi + command="AUTOBUILD_NIX_SHELL=true" + command="$command;export AUTOBUILD_NIX_SHELL" + command="$command;$(quote "$0" "$@")" + exec nix-shell --pure --command "$command" \ + -p gcc gnumake automake autoconf pkgconfig libpng zlib poppler +} + +# Gentoo +os_gentoo() { + if ! [ -e "/etc/gentoo-release" ]; then + return 1 + fi + PKGCMD=emerge + PKGARGS=--noreplace + PACKAGES="app-text/poppler + dev-util/pkgconfig + media-libs/libpng + sys-devel/autoconf + sys-devel/automake + sys-devel/gcc + sys-devel/make + sys-libs/zlib" + return 0 +} + +# By Parameter --os +os_argument() { + [ -z "$OS" ] && return 1 + case $OS in + macos) os_macos "$@";; + freebsd) os_freebsd "$@";; + arch) os_arch "$@";; + centos) os_centos "$@";; + openbsd) os_openbsd "$@";; + fedora) os_fedora "$@";; + debian) os_debian "$@";; + gentoo) os_gentoo "$@";; + msys2) os_msys2 "$@";; + nixos) os_nixos "$@";; + *) echo "Invalid --os argument: $OS" + exit 1 + esac || { + echo "Unable to install on this system as $OS" + exit 1 + } +} + +## +-----------------------------------------------------------+ +## * Figure out were we are, install deps and build the program +## +-----------------------------------------------------------+ + +handle_options "$@" + +os_argument "$@" || \ +os_macos "$@" || \ +os_freebsd "$@" || \ +os_arch "$@" || \ +os_centos "$@" || \ +os_openbsd "$@" || \ +os_fedora "$@" || \ +os_debian "$@" || \ +os_gentoo "$@" || \ +os_msys2 "$@" || \ +os_nixos "$@" || \ +{ + OS_IS_HANDLED= + if [ -z "$DRY_RUN" ]; then + echo "Failed to recognize this system, trying to continue." + fi +} + +if [ -n "$DRY_RUN" ]; then + [ -n "$OS_IS_HANDLED" ] + exit $? +fi + +if [ -n "$PKGCMD" ];then + echo "---------------------------" + echo " Installing packages " + echo "---------------------------" + if [ -n "$PKG_INSTALL_SKIP" ]; then + echo "Skipping package installation (as requested)" + elif [ -z "$PKG_INSTALL_FORCE" ] && have_packages_installed; then + echo "Skipping package installation (already installed)" + else + assert_program "$PKGCMD" + echo "$PKGCMD $PKGARGS $PACKAGES" + if [ -n "$PKG_INSTALL_AS_ROOT" ]; then + exec_privileged $PKGCMD $PKGARGS $PACKAGES + else + $PKGCMD $PKGARGS $PACKAGES + fi + fi + echo +fi + +# Try to be in the correct directory. +if which dirname >/dev/null 2>&1; then + cd "$(dirname "$0")" || { + echo "Failed to change into the source directory" + exit 1 + } +fi + +echo "---------------------------" +echo " Configuring and compiling " +echo "---------------------------" + +# Create the configure script. +if ! [ -f ./configure ]; then + assert_program autoreconf + echo "autoreconf -i" + autoreconf -i + [ -f ./configure ] || exit_fail +fi + +# Build the program. +if [ -n "$INSTALL_DIR" ]; then + prefix=--bindir=$INSTALL_DIR +fi + +echo "./configure -q $prefix && make clean && make -s" +eval "./configure -q $(quote "$prefix") && make clean && make -s || exit_fail" +echo +if [ -n "$INSTALL_DIR" ]; then + echo "---------------------------" + echo " Installing " + echo "---------------------------" + echo make -s install + if mkdir -p -- "$INSTALL_DIR" && [ -w "$INSTALL_DIR" ]; then + make install || exit_fail + else + exec_privileged make install || exit_fail + fi + # Copy dynamic libraries on windows. + if [ -f epdfinfo.exe ]; then + assert_program awk + assert_program ldd + for dll in $(ldd epdfinfo.exe | awk '/\/mingw/ {print $3}'); do + cp -u "$dll" "$INSTALL_DIR" + done + fi + echo +fi +exit_success + +# Local Variables: +# compile-command: "shellcheck autobuild" +# End: diff --git a/elpa/pdf-tools-20211110.513/build/server/autogen.sh b/elpa/pdf-tools-20211110.513/build/server/autogen.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +echo "Running autoreconf..." + +autoreconf -i diff --git a/elpa/pdf-tools-20211110.513/build/server/configure.ac b/elpa/pdf-tools-20211110.513/build/server/configure.ac @@ -0,0 +1,113 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_PREREQ([2.67]) +AC_INIT([epdfinfo], 1.0, [politza@fh-trier.de]) +AM_INIT_AUTOMAKE([-Wall -Wno-override foreign silent-rules]) +AC_CONFIG_SRCDIR([epdfinfo.h]) +AC_CONFIG_HEADERS([config.h]) + +# Checks for programs. +AC_PROG_CC +AM_PROG_CC_C_O +AC_PROG_CXX +AC_PROG_RANLIB +AM_PROG_AR + +# Checks for libraries. +HAVE_POPPLER_FIND_OPTS="no (requires poppler-glib >= 0.22)" +HAVE_POPPLER_ANNOT_WRITE="no (requires poppler-glib >= 0.19.4)" +HAVE_POPPLER_ANNOT_MARKUP="no (requires poppler-glib >= 0.26)" + +PKG_CHECK_MODULES([png], [libpng]) +PKG_CHECK_MODULES([glib], [glib-2.0]) +PKG_CHECK_MODULES([poppler], [poppler]) +PKG_CHECK_MODULES([poppler_glib], [poppler-glib >= 0.16.0]) +PKG_CHECK_EXISTS([poppler-glib >= 0.19.4], [HAVE_POPPLER_ANNOT_WRITE=yes]) +PKG_CHECK_EXISTS([poppler-glib >= 0.22], [HAVE_POPPLER_FIND_OPTS=yes]) +PKG_CHECK_EXISTS([poppler-glib >= 0.26], [HAVE_POPPLER_ANNOT_MARKUP=yes]) +PKG_CHECK_MODULES([zlib], [zlib]) + +AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[ + #ifndef _WIN32 + error + #endif + ]])], [have_w32=true], [have_w32=false]) +AM_CONDITIONAL(HAVE_W32, [test "$have_w32" = true]) + +if test "$have_w32" = true; then + if test "$MSYSTEM" = MINGW32 -o "$MSYSTEM" = MINGW64; then + # glib won't work properly on msys2 without it. + CFLAGS="-D__USE_MINGW_ANSI_STDIO=1 $CFLAGS" + fi +fi + +SAVED_CPPFLAGS=$CPPFLAGS +CPPFLAGS=$poppler_CFLAGS +AC_LANG_PUSH([C++]) +# Check if we can use the -std=c++11 option. +m4_include([m4/ax_check_compile_flag.m4]) +AX_CHECK_COMPILE_FLAG([-std=c++11], [HAVE_STD_CXX11=yes]) + +if test "$HAVE_STD_CXX11" = yes; then + CXXFLAGS="-std=c++11 $CXXFLAGS" +fi +# Check for private poppler header. +AC_CHECK_HEADERS([Annot.h PDFDocEncoding.h], [], + AC_MSG_ERROR([cannot find necessary poppler-private header (see README.org)])) +AC_LANG_POP([C++]) +CPPFLAGS=$SAVED_CPPFLAGS + +# Setup compile time features. +if test "$HAVE_POPPLER_FIND_OPTS" = yes; then + AC_DEFINE([HAVE_POPPLER_FIND_OPTS],1, + [Define to 1 to enable case sensitive searching (requires poppler-glib >= 0.22).]) +fi + +if test "$HAVE_POPPLER_ANNOT_WRITE" = yes; then + AC_DEFINE([HAVE_POPPLER_ANNOT_WRITE],1, + [Define to 1 to enable writing of annotations (requires poppler-glib >= 0.19.4).]) +fi + +if test "$HAVE_POPPLER_ANNOT_MARKUP" = yes; then + AC_DEFINE([HAVE_POPPLER_ANNOT_MARKUP],1, + [Define to 1 to enable adding of markup annotations (requires poppler-glib >= 0.26).]) +fi + +AC_CANONICAL_HOST +# Checks for header files. +AC_CHECK_HEADERS([stdlib.h string.h strings.h err.h]) + +AC_MSG_CHECKING([for error.h]) +SAVED_CFLAGS=$CFLAGS +CFLAGS="$poppler_CFLAGS $poppler_glib_CFLAGS" +AC_LANG_PUSH([C]) +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([ + #include <error.h> + ],[error (0, 0, "");])], + [AC_DEFINE([HAVE_ERROR_H],1, [Define to 1 if error.h is usable.]) + AC_MSG_RESULT([yes])], + AC_MSG_RESULT([no])) +AC_LANG_POP([C]) +CFLAGS=$SAVED_CFLAGS + +# Checks for typedefs, structures, and compiler characteristics. +AC_TYPE_SIZE_T +AC_TYPE_SSIZE_T +AC_CHECK_TYPES([ptrdiff_t]) +AC_C_BIGENDIAN + +# Checks for library functions. +AC_FUNC_ERROR_AT_LINE +AC_FUNC_STRTOD +AC_CHECK_FUNCS([strcspn strtol getline]) + +AC_CONFIG_FILES([Makefile]) +AC_OUTPUT + +echo +echo "Is case-sensitive searching enabled ? ${HAVE_POPPLER_FIND_OPTS}" +echo "Is modifying text annotations enabled ? ${HAVE_POPPLER_ANNOT_WRITE}" +echo "Is modifying markup annotations enabled ? ${HAVE_POPPLER_ANNOT_MARKUP}" +echo diff --git a/elpa/pdf-tools-20211110.513/build/server/epdfinfo.c b/elpa/pdf-tools-20211110.513/build/server/epdfinfo.c @@ -0,0 +1,3718 @@ +/* Copyright (C) 2013, 2014 Andreas Politz + * + * Author: Andreas Politz <politza@fh-trier.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include <config.h> + +#include <assert.h> +#ifdef HAVE_ERR_H +# include <err.h> +#endif +#ifdef HAVE_ERROR_H +# include <error.h> +#endif +#include <glib.h> +#include <poppler.h> +#include <cairo.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> +#include <png.h> +#include <math.h> +#include <regex.h> +#include "synctex_parser.h" +#include "epdfinfo.h" + + +/* ================================================================== * + * Helper Functions + * ================================================================== */ + +#ifndef HAVE_ERR_H +/** + * Print error message and quit. + * + * @param eval Return code + * @param fmt Formatting string + */ +static void +err(int eval, const char *fmt, ...) +{ + va_list args; + + fprintf (stderr, "epdfinfo: "); + if (fmt != NULL) + { + va_start (args, fmt); + vfprintf (stderr, fmt, args); + va_end (args); + fprintf (stderr, ": %s\n", strerror(errno)); + } + else + { + fprintf (stderr, "\n"); + } + + fflush (stderr); + exit (eval); +} +#endif + +#ifndef HAVE_GETLINE +/** + * Read one line from a file. + * + * @param lineptr Pointer to malloc() allocated buffer + * @param n Pointer to size of buffer + * @param stream File pointer to read from + */ +static ssize_t +getline(char **lineptr, size_t *n, FILE *stream) +{ + size_t len = 0; + int ch; + + if ((lineptr == NULL) || (n == NULL)) + { + errno = EINVAL; + return -1; + } + + if (*lineptr == NULL) + { + *lineptr = malloc (128); + *n = 128; + } + + while ((ch = fgetc (stream)) != EOF) + { + (*lineptr)[len] = ch; + + if (++len >= *n) + { + *n += 128; + *lineptr = realloc (*lineptr, *n); + } + + if (ch == '\n') + break; + } + (*lineptr)[len] = '\0'; + + if (!len) + { + len = -1; + } + + return len; +} +#endif + +/** + * Free a list of command arguments. + * + * @param args An array of command arguments. + * @param n The length of the array. + */ +static void +free_command_args (command_arg_t *args, size_t n) +{ + if (! args) + return; + + g_free (args); +} + +/** + * Free resources held by document. + * + * @param doc The document to be freed. + */ +static void +free_document (document_t *doc) +{ + if (! doc) + return; + + g_free (doc->filename); + g_free (doc->passwd); + if (doc->annotations.pages) + { + int npages = poppler_document_get_n_pages (doc->pdf); + int i; + for (i = 0; i < npages; ++i) + { + GList *item; + GList *annots = doc->annotations.pages[i]; + for (item = annots; item; item = item->next) + { + annotation_t *a = (annotation_t*) item->data; + poppler_annot_mapping_free(a->amap); + g_free (a->key); + g_free (a); + } + g_list_free (annots); + } + g_hash_table_destroy (doc->annotations.keys); + g_free (doc->annotations.pages); + } + g_object_unref (doc->pdf); + g_free (doc); +} + +/** + * Parse a list of whitespace separated double values. + * + * @param str The input string. + * @param values[out] Values are put here. + * @param nvalues How many values to parse. + * + * @return TRUE, if str contained exactly nvalues, else FALSE. + */ +static gboolean +parse_double_list (const char *str, gdouble *values, size_t nvalues) +{ + char *end; + int i; + + if (! str) + return FALSE; + + errno = 0; + for (i = 0; i < nvalues; ++i) + { + gdouble n = g_ascii_strtod (str, &end); + + if (str == end || errno) + return FALSE; + + values[i] = n; + str = end; + } + + if (*end) + return FALSE; + + return TRUE; +} + +static gboolean +parse_rectangle (const char *str, PopplerRectangle *r) +{ + gdouble values[4]; + + if (! r) + return FALSE; + + if (! parse_double_list (str, values, 4)) + return FALSE; + + r->x1 = values[0]; + r->y1 = values[1]; + r->x2 = values[2]; + r->y2 = values[3]; + + return TRUE; +} + +static gboolean +parse_edges_or_position (const char *str, PopplerRectangle *r) +{ + return (parse_rectangle (str, r) + && r->x1 >= 0 && r->x1 <= 1 + && r->x2 <= 1 + && r->y1 >= 0 && r->y1 <= 1 + && r->y2 <= 1); +} + +static gboolean +parse_edges (const char *str, PopplerRectangle *r) +{ + return (parse_rectangle (str, r) + && r->x1 >= 0 && r->x1 <= 1 + && r->x2 >= 0 && r->x2 <= 1 + && r->y1 >= 0 && r->y1 <= 1 + && r->y2 >= 0 && r->y2 <= 1); +} + +/** + * Print a string properly escaped for a response. + * + * @param str The string to be printed. + * @param suffix_char Append a newline if NEWLINE, a colon if COLON. + */ +static void +print_response_string (const char *str, enum suffix_char suffix) +{ + if (str) + { + while (*str) + { + switch (*str) + { + case '\n': + printf ("\\n"); + break; + case '\\': + printf ("\\\\"); + break; + case ':': + printf ("\\:"); + break; + default: + putchar (*str); + } + ++str; + } + } + + switch (suffix) + { + case NEWLINE: + putchar ('\n'); + break; + case COLON: + putchar (':'); + break; + default: ; + } +} + + +/** + * Print a formatted error response. + * + * @param fmt The printf-like format string. + */ +static void +printf_error_response (const char *fmt, ...) +{ + va_list va; + puts ("ERR"); + va_start (va, fmt); + vprintf (fmt, va); + va_end (va); + puts ("\n."); + fflush (stdout); +} + +/** + * Remove one trailing newline character. Does nothing, if str does + * not end with a newline. + * + * @param str The string. + * + * @return str with trailing newline removed. + */ +static char* +strchomp (char *str) +{ + size_t length; + + if (! str) + return str; + + length = strlen (str); + if (str[length - 1] == '\n') + str[length - 1] = '\0'; + + return str; +} + +/** + * Create a new, temporary file and returns it's name. + * + * @return The filename. + */ +static char* +mktempfile() +{ + char *filename = NULL; + int tries = 3; + while (! filename && tries-- > 0) + { + + filename = tempnam(NULL, "epdfinfo"); + if (filename) + { + int fd = open(filename, O_CREAT | O_EXCL | O_RDONLY, S_IRWXU); + if (fd > 0) + close (fd); + else + { + free (filename); + filename = NULL; + } + } + } + if (! filename) + fprintf (stderr, "Unable to create tempfile"); + + return filename; +} + +static void +image_recolor (cairo_surface_t * surface, const PopplerColor * fg, + const PopplerColor * bg) +{ + /* uses a representation of a rgb color as follows: + - a lightness scalar (between 0,1), which is a weighted average of r, g, b, + - a hue vector, which indicates a radian direction from the grey axis, + inside the equal lightness plane. + - a saturation scalar between 0,1. It is 0 when grey, 1 when the color is + in the boundary of the rgb cube. + */ + + const unsigned int page_width = cairo_image_surface_get_width (surface); + const unsigned int page_height = cairo_image_surface_get_height (surface); + const int rowstride = cairo_image_surface_get_stride (surface); + unsigned char *image = cairo_image_surface_get_data (surface); + + /* RGB weights for computing lightness. Must sum to one */ + static const double a[] = { 0.30, 0.59, 0.11 }; + + const double f = 65535.; + const double rgb_fg[] = { + fg->red / f, fg->green / f, fg->blue / f + }; + const double rgb_bg[] = { + bg->red / f, bg->green / f, bg->blue / f + }; + + const double rgb_diff[] = { + rgb_bg[0] - rgb_fg[0], + rgb_bg[1] - rgb_fg[1], + rgb_bg[2] - rgb_fg[2] + }; + + unsigned int y; + for (y = 0; y < page_height * rowstride; y += rowstride) + { + unsigned char *data = image + y; + + unsigned int x; + for (x = 0; x < page_width; x++, data += 4) + { + /* Careful. data color components blue, green, red. */ + const double rgb[3] = { + (double) data[2] / 256., + (double) data[1] / 256., + (double) data[0] / 256. + }; + + /* compute h, s, l data */ + double l = a[0] * rgb[0] + a[1] * rgb[1] + a[2] * rgb[2]; + + /* linear interpolation between dark and light with color ligtness as + * a parameter */ + data[2] = + (unsigned char) round (255. * (l * rgb_diff[0] + rgb_fg[0])); + data[1] = + (unsigned char) round (255. * (l * rgb_diff[1] + rgb_fg[1])); + data[0] = + (unsigned char) round (255. * (l * rgb_diff[2] + rgb_fg[2])); + } + } +} + +/** + * Render a PDF page. + * + * @param pdf The PDF document. + * @param page The page to be rendered. + * @param width The desired width of the image. + * + * @return A cairo_t context encapsulating the rendered image, or + * NULL, if rendering failed for some reason. + */ +static cairo_surface_t* +image_render_page(PopplerDocument *pdf, PopplerPage *page, + int width, gboolean do_render_annotaions, + const render_options_t *options) +{ + cairo_t *cr = NULL; + cairo_surface_t *surface = NULL; + double pt_width, pt_height; + int height; + double scale = 1; + + if (! page || ! pdf) + return NULL; + + if (width < 1) + width = 1; + + poppler_page_get_size (page, &pt_width, &pt_height); + scale = width / pt_width; + height = (int) ((scale * pt_height) + 0.5); + + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, + width, height); + + if (cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS) + { + fprintf (stderr, "Failed to create cairo surface\n"); + goto error; + } + + cr = cairo_create (surface); + if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) + { + fprintf (stderr, "Failed to create cairo handle\n"); + goto error; + } + + cairo_translate (cr, 0, 0); + cairo_scale (cr, scale, scale); + /* Render w/o annotations. */ + if (! do_render_annotaions || (options && options->printed)) + poppler_page_render_for_printing_with_options + (page, cr, POPPLER_PRINT_DOCUMENT); + else + poppler_page_render (page, cr) ; + if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) + { + fprintf (stderr, "Failed to render page\n"); + goto error; + } + + /* This makes the colors look right. */ + cairo_set_operator (cr, CAIRO_OPERATOR_DEST_OVER); + cairo_set_source_rgb (cr, 1., 1., 1.); + + cairo_paint (cr); + + if (options && options->usecolors) + image_recolor (surface, &options->fg, &options->bg); + + cairo_destroy (cr); + + return surface; + + error: + if (surface != NULL) + cairo_surface_destroy (surface); + if (cr != NULL) + cairo_destroy (cr); + return NULL; +} + +/** + * Write an image to a filename. + * + * @param cr The cairo context encapsulating the image. + * @param filename The filename to be written to. + * @param type The desired image type. + * + * @return 1 if the image was written successfully, else 0. + */ +static gboolean +image_write (cairo_surface_t *surface, const char *filename, enum image_type type) +{ + + int i, j; + unsigned char *data; + int width, height; + FILE *file = NULL; + gboolean success = 0; + + if (! surface || + cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) + { + fprintf (stderr, "Invalid cairo surface\n"); + return 0; + } + + if (! (file = fopen (filename, "wb"))) + { + fprintf (stderr, "Can not open file: %s\n", filename); + return 0; + } + + cairo_surface_flush (surface); + width = cairo_image_surface_get_width (surface); + height = cairo_image_surface_get_height (surface); + data = cairo_image_surface_get_data (surface); + + switch (type) + { + case PPM: + { + unsigned char *buffer = g_malloc (width * height * 3); + unsigned char *buffer_p = buffer; + + fprintf (file, "P6\n%d %d\n255\n", width, height); + for (i = 0; i < width * height; ++i, data += 4, buffer_p += 3) + ARGB_TO_RGB (buffer_p, data); + fwrite (buffer, 1, width * height * 3, file); + g_free (buffer); + success = 1; + } + break; + case PNG: + { + png_infop info_ptr = NULL; + png_structp png_ptr = NULL; + unsigned char *row = NULL; + + png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_ptr) + goto finalize; + + info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) + goto finalize; + + if (setjmp(png_jmpbuf(png_ptr))) + goto finalize; + + png_init_io (png_ptr, file); + png_set_compression_level (png_ptr, 1); + png_set_IHDR (png_ptr, info_ptr, width, height, + 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, + PNG_FILTER_TYPE_DEFAULT); + + png_set_filter (png_ptr, PNG_FILTER_TYPE_BASE, + PNG_FILTER_NONE); + png_write_info (png_ptr, info_ptr); + row = g_malloc (3 * width); + for (i = 0; i < height; ++i) + { + unsigned char *row_p = row; + for (j = 0; j < width; ++j, data += 4, row_p += 3) + { + ARGB_TO_RGB (row_p, data); + } + png_write_row (png_ptr, row); + } + png_write_end (png_ptr, NULL); + success = 1; + finalize: + if (png_ptr) + png_destroy_write_struct (&png_ptr, &info_ptr); + if (row) + g_free (row); + if (! success) + fprintf (stderr, "Error writing png data\n"); + } + break; + default: + internal_error ("switch fell through"); + } + + fclose (file); + return success; +} + +static void +image_write_print_response(cairo_surface_t *surface, enum image_type type) +{ + char *filename = mktempfile (); + + perror_if_not (filename, "Unable to create temporary file"); + if (image_write (surface, filename, type)) + { + OK_BEGIN (); + print_response_string (filename, NEWLINE); + OK_END (); + } + else + { + printf_error_response ("Unable to write image"); + } + free (filename); + error: + return; +} + +static void +region_print (cairo_region_t *region, double width, double height) +{ + int i; + + for (i = 0; i < cairo_region_num_rectangles (region); ++i) + { + cairo_rectangle_int_t r; + + cairo_region_get_rectangle (region, i, &r); + printf ("%f %f %f %f", + r.x / width, + r.y / height, + (r.x + r.width) / width, + (r.y + r.height) / height); + if (i < cairo_region_num_rectangles (region) - 1) + putchar (':'); + } + if (0 == cairo_region_num_rectangles (region)) + printf ("0.0 0.0 0.0 0.0"); +} + +/** + * Return a string representation of a PopplerActionType. + * + * @param type The PopplerActionType. + * + * @return It's string representation. + */ +static const char * +xpoppler_action_type_string(PopplerActionType type) +{ + switch (type) + { + case POPPLER_ACTION_UNKNOWN: return "unknown"; + case POPPLER_ACTION_NONE: return "none"; + case POPPLER_ACTION_GOTO_DEST: return "goto-dest"; + case POPPLER_ACTION_GOTO_REMOTE: return "goto-remote"; + case POPPLER_ACTION_LAUNCH: return "launch"; + case POPPLER_ACTION_URI: return "uri"; + case POPPLER_ACTION_NAMED: return "goto-dest"; /* actually "named" */ + case POPPLER_ACTION_MOVIE: return "movie"; + case POPPLER_ACTION_RENDITION: return "rendition"; + case POPPLER_ACTION_OCG_STATE: return "ocg-state"; + case POPPLER_ACTION_JAVASCRIPT: return "javascript"; + default: return "invalid"; + } +} + +/** + * Return a string representation of a PopplerAnnotType. + * + * @param type The PopplerAnnotType. + * + * @return It's string representation. + */ +static const char * +xpoppler_annot_type_string (PopplerAnnotType type) +{ + switch (type) + { + case POPPLER_ANNOT_UNKNOWN: return "unknown"; + case POPPLER_ANNOT_TEXT: return "text"; + case POPPLER_ANNOT_LINK: return "link"; + case POPPLER_ANNOT_FREE_TEXT: return "free-text"; + case POPPLER_ANNOT_LINE: return "line"; + case POPPLER_ANNOT_SQUARE: return "square"; + case POPPLER_ANNOT_CIRCLE: return "circle"; + case POPPLER_ANNOT_POLYGON: return "polygon"; + case POPPLER_ANNOT_POLY_LINE: return "poly-line"; + case POPPLER_ANNOT_HIGHLIGHT: return "highlight"; + case POPPLER_ANNOT_UNDERLINE: return "underline"; + case POPPLER_ANNOT_SQUIGGLY: return "squiggly"; + case POPPLER_ANNOT_STRIKE_OUT: return "strike-out"; + case POPPLER_ANNOT_STAMP: return "stamp"; + case POPPLER_ANNOT_CARET: return "caret"; + case POPPLER_ANNOT_INK: return "ink"; + case POPPLER_ANNOT_POPUP: return "popup"; + case POPPLER_ANNOT_FILE_ATTACHMENT: return "file"; + case POPPLER_ANNOT_SOUND: return "sound"; + case POPPLER_ANNOT_MOVIE: return "movie"; + case POPPLER_ANNOT_WIDGET: return "widget"; + case POPPLER_ANNOT_SCREEN: return "screen"; + case POPPLER_ANNOT_PRINTER_MARK: return "printer-mark"; + case POPPLER_ANNOT_TRAP_NET: return "trap-net"; + case POPPLER_ANNOT_WATERMARK: return "watermark"; + case POPPLER_ANNOT_3D: return "3d"; + default: return "invalid"; + } +} + +/** + * Return a string representation of a PopplerAnnotTextState. + * + * @param type The PopplerAnnotTextState. + * + * @return It's string representation. + */ +static const char * +xpoppler_annot_text_state_string (PopplerAnnotTextState state) +{ + switch (state) + { + case POPPLER_ANNOT_TEXT_STATE_MARKED: return "marked"; + case POPPLER_ANNOT_TEXT_STATE_UNMARKED: return "unmarked"; + case POPPLER_ANNOT_TEXT_STATE_ACCEPTED: return "accepted"; + case POPPLER_ANNOT_TEXT_STATE_REJECTED: return "rejected"; + case POPPLER_ANNOT_TEXT_STATE_CANCELLED: return "cancelled"; + case POPPLER_ANNOT_TEXT_STATE_COMPLETED: return "completed"; + case POPPLER_ANNOT_TEXT_STATE_NONE: return "none"; + case POPPLER_ANNOT_TEXT_STATE_UNKNOWN: + default: return "unknown"; + } +}; + +static document_t* +document_open (const epdfinfo_t *ctx, const char *filename, + const char *passwd, GError **gerror) +{ + char *uri; + document_t *doc = g_hash_table_lookup (ctx->documents, filename); + + if (NULL != doc) + return doc; + + doc = g_malloc0(sizeof (document_t)); + uri = g_filename_to_uri (filename, NULL, gerror); + if (uri != NULL) + doc->pdf = poppler_document_new_from_file(uri, passwd, gerror); + + if (NULL == doc->pdf) + { + g_free (doc); + doc = NULL; + } + else + { + doc->filename = g_strdup (filename); + doc->passwd = g_strdup (passwd); + g_hash_table_insert (ctx->documents, doc->filename, doc); + } + g_free (uri); + return doc; +} + +/** + * Split command args into a list of strings. + * + * @param args The colon separated list of arguments. + * @param nargs[out] The number of returned arguments. + * + * @return The list of arguments, which should be freed by the caller. + */ +static char ** +command_arg_split (const char *args, int *nargs) +{ + char **list = g_malloc (sizeof (char*) * 16); + int i = 0; + size_t allocated = 16; + char *buffer = NULL; + gboolean last = FALSE; + + if (! args) + goto theend; + + buffer = g_malloc (strlen (args) + 1); + + while (*args || last) + { + gboolean esc = FALSE; + char *buffer_p = buffer; + + while (*args && (*args != ':' || esc)) + { + if (esc) + { + if (*args == 'n') + { + ++args; + *buffer_p++ = '\n'; + } + else + { + *buffer_p++ = *args++; + } + esc = FALSE; + } + else if (*args == '\\') + { + ++args; + esc = TRUE; + } + else + { + *buffer_p++ = *args++; + } + } + + *buffer_p = '\0'; + + if (i >= allocated) + { + allocated = 2 * allocated + 1; + list = g_realloc (list, sizeof (char*) * allocated); + } + list[i++] = g_strdup (buffer); + + last = FALSE; + if (*args) + { + ++args; + if (! *args) + last = TRUE; + } + } + + theend: + g_free (buffer); + *nargs = i; + + return list; +} + +static gboolean +command_arg_parse_arg (const epdfinfo_t *ctx, const char *arg, + command_arg_t *cmd_arg, command_arg_type_t type, + gchar **error_msg) +{ + GError *gerror = NULL; + + if (! arg || !cmd_arg) + return FALSE; + + switch (type) + { + case ARG_DOC: + { + document_t *doc = document_open (ctx, arg, NULL, &gerror); + cerror_if_not (doc, error_msg, + "Error opening %s:%s", arg, + gerror ? gerror->message : "Unknown reason"); + + cmd_arg->value.doc = doc; + break; + } + case ARG_BOOL: + cerror_if_not (! strcmp (arg, "0") || ! strcmp (arg, "1"), + error_msg, "Expected 0 or 1:%s", arg); + cmd_arg->value.flag = *arg == '1'; + break; + case ARG_NONEMPTY_STRING: + cerror_if_not (*arg, error_msg, "Non-empty string expected"); + /* fall through */ + case ARG_STRING: + cmd_arg->value.string = arg; + break; + case ARG_NATNUM: + { + char *endptr; + long n = strtol (arg, &endptr, 0); + cerror_if_not (! (*endptr || (n < 0)), error_msg, + "Expected natural number:%s", arg); + cmd_arg->value.natnum = n; + } + break; + case ARG_EDGES_OR_POSITION: + { + PopplerRectangle *r = &cmd_arg->value.rectangle; + cerror_if_not (parse_edges_or_position (arg, r), + error_msg, + "Expected a relative position or rectangle: %s", arg); + } + break; + case ARG_EDGES: + { + PopplerRectangle *r = &cmd_arg->value.rectangle; + cerror_if_not (parse_edges (arg, r), + error_msg, + "Expected a relative rectangle: %s", arg); + } + break; + case ARG_EDGE_OR_NEGATIVE: + case ARG_EDGE: + { + char *endptr; + double n = strtod (arg, &endptr); + cerror_if_not (! (*endptr || (type != ARG_EDGE_OR_NEGATIVE && n < 0.0) || n > 1.0), + error_msg, "Expected a relative edge: %s", arg); + cmd_arg->value.edge = n; + } + break; + case ARG_COLOR: + { + guint r,g,b; + cerror_if_not ((strlen (arg) == 7 + && 3 == sscanf (arg, "#%2x%2x%2x", &r, &g, &b)), + error_msg, "Invalid color: %s", arg); + cmd_arg->value.color.red = r << 8; + cmd_arg->value.color.green = g << 8; + cmd_arg->value.color.blue = b << 8; + } + break; + case ARG_INVALID: + default: + internal_error ("switch fell through"); + } + + cmd_arg->type = type; + + return TRUE; + error: + if (gerror) + { + g_error_free (gerror); + gerror = NULL; + } + return FALSE; +} + +/** + * Parse arguments for a command. + * + * @param ctx The epdfinfo context. + * @param args A string holding the arguments. This is either empty + * or the suffix of the command starting at the first + * colon after the command name. + * @param len The length of args. + * @param cmd The command for which the arguments should be parsed. + * + * @return + */ +static command_arg_t* +command_arg_parse(epdfinfo_t *ctx, char **args, int nargs, + const command_t *cmd, gchar **error_msg) +{ + command_arg_t *cmd_args = g_malloc0 (cmd->nargs * sizeof (command_arg_t)); + int i; + + if (nargs < cmd->nargs - 1 + || (nargs == cmd->nargs - 1 + && cmd->args_spec[cmd->nargs - 1] != ARG_REST) + || (nargs > cmd->nargs + && (cmd->nargs == 0 + || cmd->args_spec[cmd->nargs - 1] != ARG_REST))) + { + if (error_msg) + { + *error_msg = + g_strdup_printf ("Command `%s' expects %d argument(s), %d given", + cmd->name, cmd->nargs, nargs); + } + goto failure; + } + + for (i = 0; i < cmd->nargs; ++i) + { + if (i == cmd->nargs - 1 && cmd->args_spec[i] == ARG_REST) + { + cmd_args[i].value.rest.args = args + i; + cmd_args[i].value.rest.nargs = nargs - i; + cmd_args[i].type = ARG_REST; + } + else if (i >= nargs + || ! command_arg_parse_arg (ctx, args[i], cmd_args + i, + cmd->args_spec[i], error_msg)) + { + goto failure; + } + } + + return cmd_args; + + failure: + free_command_args (cmd_args, cmd->nargs); + return NULL; +} + +static void +command_arg_print(const command_arg_t *arg) +{ + switch (arg->type) + { + case ARG_INVALID: + printf ("[invalid]"); + break; + case ARG_DOC: + print_response_string (arg->value.doc->filename, NONE); + break; + case ARG_BOOL: + printf ("%d", arg->value.flag ? 1 : 0); + break; + case ARG_NONEMPTY_STRING: /* fall */ + case ARG_STRING: + print_response_string (arg->value.string, NONE); + break; + case ARG_NATNUM: + printf ("%ld", arg->value.natnum); + break; + case ARG_EDGE_OR_NEGATIVE: /* fall */ + case ARG_EDGE: + printf ("%f", arg->value.edge); + break; + case ARG_EDGES_OR_POSITION: /* fall */ + case ARG_EDGES: + { + const PopplerRectangle *r = &arg->value.rectangle; + if (r->x2 < 0 && r->y2 < 0) + printf ("%f %f", r->x1, r->y1); + else + printf ("%f %f %f %f", r->x1, r->y1, r->x2, r->y2); + break; + } + case ARG_COLOR: + { + const PopplerColor *c = &arg->value.color; + printf ("#%.2x%.2x%.2x", c->red >> 8, + c->green >> 8, c->blue >> 8); + break; + } + case ARG_REST: + { + int i; + for (i = 0; i < arg->value.rest.nargs; ++i) + print_response_string (arg->value.rest.args[i], COLON); + if (arg->value.rest.nargs > 0) + print_response_string (arg->value.rest.args[i], NONE); + break; + } + default: + internal_error ("switch fell through"); + } +} + +static size_t +command_arg_type_size(command_arg_type_t type) +{ + command_arg_t arg; + switch (type) + { + case ARG_INVALID: return 0; + case ARG_DOC: return sizeof (arg.value.doc); + case ARG_BOOL: return sizeof (arg.value.flag); + case ARG_NONEMPTY_STRING: /* fall */ + case ARG_STRING: return sizeof (arg.value.string); + case ARG_NATNUM: return sizeof (arg.value.natnum); + case ARG_EDGE_OR_NEGATIVE: /* fall */ + case ARG_EDGE: return sizeof (arg.value.edge); + case ARG_EDGES_OR_POSITION: /* fall */ + case ARG_EDGES: return sizeof (arg.value.rectangle); + case ARG_COLOR: return sizeof (arg.value.color); + case ARG_REST: return sizeof (arg.value.rest); + default: + internal_error ("switch fell through"); + return 0; + } +} + + +/* ------------------------------------------------------------------ * + * PDF Actions + * ------------------------------------------------------------------ */ + +static gboolean +action_is_handled (PopplerAction *action) +{ + if (! action) + return FALSE; + + switch (action->any.type) + { + case POPPLER_ACTION_GOTO_REMOTE: + case POPPLER_ACTION_GOTO_DEST: + case POPPLER_ACTION_NAMED: + /* case POPPLER_ACTION_LAUNCH: */ + case POPPLER_ACTION_URI: + return TRUE; + default: ; + } + return FALSE; +} + +static void +action_print_destination (PopplerDocument *doc, PopplerAction *action) +{ + PopplerDest *dest = NULL; + gboolean free_dest = FALSE; + double width, height, top; + PopplerPage *page; + int saved_stdin; + + if (action->any.type == POPPLER_ACTION_GOTO_DEST + && action->goto_dest.dest->type == POPPLER_DEST_NAMED) + { + DISCARD_STDOUT (saved_stdin); + /* poppler_document_find_dest reports errors to stdout, so + discard them. */ + dest = poppler_document_find_dest + (doc, action->goto_dest.dest->named_dest); + UNDISCARD_STDOUT (saved_stdin); + free_dest = TRUE; + } + else if (action->any.type == POPPLER_ACTION_NAMED) + + { + DISCARD_STDOUT (saved_stdin); + dest = poppler_document_find_dest (doc, action->named.named_dest); + UNDISCARD_STDOUT (saved_stdin); + free_dest = TRUE; + } + + else if (action->any.type == POPPLER_ACTION_GOTO_REMOTE) + { + print_response_string (action->goto_remote.file_name, COLON); + dest = action->goto_remote.dest; + } + else if (action->any.type == POPPLER_ACTION_GOTO_DEST) + dest = action->goto_dest.dest; + + if (!dest + || dest->type == POPPLER_DEST_UNKNOWN + || dest->page_num < 1 + || dest->page_num > poppler_document_get_n_pages (doc)) + { + printf (":"); + goto theend; + } + + printf ("%d:", dest->page_num); + + if (action->type == POPPLER_ACTION_GOTO_REMOTE + || NULL == (page = poppler_document_get_page (doc, dest->page_num - 1))) + { + goto theend; + } + + poppler_page_get_size (page, &width, &height); + g_object_unref (page); + top = (height - dest->top) / height; + + /* adapted from xpdf */ + switch (dest->type) + { + case POPPLER_DEST_XYZ: + if (dest->change_top) + printf ("%f", top); + break; + case POPPLER_DEST_FIT: + case POPPLER_DEST_FITB: + case POPPLER_DEST_FITH: + case POPPLER_DEST_FITBH: + putchar ('0'); + break; + case POPPLER_DEST_FITV: + case POPPLER_DEST_FITBV: + case POPPLER_DEST_FITR: + printf ("%f", top); + break; + default: ; + } + + theend: + if (free_dest) + poppler_dest_free (dest); +} + +static void +action_print (PopplerDocument *doc, PopplerAction *action) +{ + if (! action_is_handled (action)) + return; + + print_response_string (xpoppler_action_type_string (action->any.type), COLON); + print_response_string (action->any.title, COLON); + switch (action->any.type) + { + case POPPLER_ACTION_GOTO_REMOTE: + case POPPLER_ACTION_GOTO_DEST: + case POPPLER_ACTION_NAMED: + action_print_destination (doc, action); + putchar ('\n'); + break; + case POPPLER_ACTION_LAUNCH: + print_response_string (action->launch.file_name, COLON); + print_response_string (action->launch.params, NEWLINE); + break; + case POPPLER_ACTION_URI: + print_response_string (action->uri.uri, NEWLINE); + break; + default: + ; + } +} + + +/* ------------------------------------------------------------------ * + * PDF Annotations and Attachments + * ------------------------------------------------------------------ */ + +/* static gint + * annotation_cmp_edges (const annotation_t *a1, const annotation_t *a2) + * { + * PopplerRectangle *e1 = &a1->amap->area; + * PopplerRectangle *e2 = &a2->amap->area; + * + * return (e1->y1 > e2->y1 ? -1 + * : e1->y1 < e2->y1 ? 1 + * : e1->x1 < e2->x1 ? -1 + * : e1->x1 != e2->x1); + * } */ + +static GList* +annoation_get_for_page (document_t *doc, gint pn) +{ + + GList *annot_list, *item; + PopplerPage *page; + gint i = 0; + gint npages = poppler_document_get_n_pages (doc->pdf); + + if (pn < 1 || pn > npages) + return NULL; + + if (! doc->annotations.pages) + doc->annotations.pages = g_malloc0 (npages * sizeof(GList*)); + + if (doc->annotations.pages[pn - 1]) + return doc->annotations.pages[pn - 1]; + + if (! doc->annotations.keys) + doc->annotations.keys = g_hash_table_new (g_str_hash, g_str_equal); + + page = poppler_document_get_page (doc->pdf, pn - 1); + if (NULL == page) + return NULL; + + annot_list = poppler_page_get_annot_mapping (page); + for (item = annot_list; item; item = item->next) + { + PopplerAnnotMapping *map = (PopplerAnnotMapping *)item->data; + gchar *key = g_strdup_printf ("annot-%d-%d", pn, i); + annotation_t *a = g_malloc (sizeof (annotation_t)); + a->amap = map; + a->key = key; + doc->annotations.pages[pn - 1] = + g_list_prepend (doc->annotations.pages[pn - 1], a); + assert (NULL == g_hash_table_lookup (doc->annotations.keys, key)); + g_hash_table_insert (doc->annotations.keys, key, a); + ++i; + } + g_list_free (annot_list); + g_object_unref (page); + return doc->annotations.pages[pn - 1]; +} + +static annotation_t* +annotation_get_by_key (document_t *doc, const gchar *key) +{ + if (! doc->annotations.keys) + return NULL; + + return g_hash_table_lookup (doc->annotations.keys, key); +} + +#ifdef HAVE_POPPLER_ANNOT_MARKUP +void +annotation_translate_quadrilateral (PopplerPage *page, PopplerQuadrilateral *q, gboolean inverse) +{ + PopplerRectangle cbox; + gdouble xs, ys; + + poppler_page_get_crop_box (page, &cbox); + xs = MIN (cbox.x1, cbox.x2); + ys = MIN (cbox.y1, cbox.y2); + + if (inverse) + { + xs = -xs; ys = -ys; + } + + q->p1.x -= xs, q->p2.x -= xs; q->p3.x -= xs; q->p4.x -= xs; + q->p1.y -= ys, q->p2.y -= ys; q->p3.y -= ys; q->p4.y -= ys; +} + +static cairo_region_t* +annotation_markup_get_text_regions (PopplerPage *page, PopplerAnnotTextMarkup *a) +{ + GArray *quads = poppler_annot_text_markup_get_quadrilaterals (a); + int i; + cairo_region_t *region = cairo_region_create (); + gdouble height; + + poppler_page_get_size (page, NULL, &height); + + for (i = 0; i < quads->len; ++i) + { + PopplerQuadrilateral *q = &g_array_index (quads, PopplerQuadrilateral, i); + cairo_rectangle_int_t r; + + annotation_translate_quadrilateral (page, q, FALSE); + q->p1.y = height - q->p1.y; + q->p2.y = height - q->p2.y; + q->p3.y = height - q->p3.y; + q->p4.y = height - q->p4.y; + + r.x = (int) (MIN (q->p1.x, MIN (q->p2.x, MIN (q->p3.x, q->p4.x))) + 0.5); + r.y = (int) (MIN (q->p1.y, MIN (q->p2.y, MIN (q->p3.y, q->p4.y))) + 0.5); + r.width = (int) (MAX (q->p1.x, MAX (q->p2.x, MAX (q->p3.x, q->p4.x))) + 0.5) + - r.x; + r.height = (int) (MAX (q->p1.y, MAX (q->p2.y, MAX (q->p3.y, q->p4.y))) + 0.5) + - r.y; + + cairo_region_union_rectangle (region, &r); + } + g_array_unref (quads); + return region; +} + +/** + * Append quadrilaterals equivalent to region to an array. + * + * @param page The page of the annotation. This is used to get the + * text regions and pagesize. + * @param region The region to add. + * @param garray[in,out] An array of PopplerQuadrilateral, where the + * new quadrilaterals will be appended. + */ +static void +annotation_markup_append_text_region (PopplerPage *page, PopplerRectangle *region, + GArray *garray) +{ + gdouble height; + /* poppler_page_get_selection_region is deprecated w/o a + replacement. (poppler_page_get_selected_region returns a union + of rectangles.) */ + GList *regions = + poppler_page_get_selection_region (page, 1.0, POPPLER_SELECTION_GLYPH, region); + GList *item; + + poppler_page_get_size (page, NULL, &height); + for (item = regions; item; item = item->next) + { + PopplerRectangle *r = item->data; + PopplerQuadrilateral q; + + q.p1.x = r->x1; + q.p1.y = height - r->y1; + q.p2.x = r->x2; + q.p2.y = height - r->y1; + q.p4.x = r->x2; + q.p4.y = height - r->y2; + q.p3.x = r->x1; + q.p3.y = height - r->y2; + + annotation_translate_quadrilateral (page, &q, TRUE); + g_array_append_val (garray, q); + } + g_list_free (regions); +} + +#endif +/** + * Create a new annotation. + * + * @param doc The document for which to create it. + * @param type The type of the annotation. + * @param r The rectangle where annotation will end up on the page. + * + * @return The new annotation, or NULL, if the annotation type is + * not available. + */ +static PopplerAnnot* +annotation_new (const epdfinfo_t *ctx, document_t *doc, PopplerPage *page, + const char *type, PopplerRectangle *r, + const command_arg_t *rest, char **error_msg) +{ + + PopplerAnnot *a = NULL; + int nargs = rest->value.rest.nargs; +#ifdef HAVE_POPPLER_ANNOT_MARKUP + char * const *args = rest->value.rest.args; + int i; + GArray *garray = NULL; + command_arg_t carg; + double width, height; + cairo_region_t *region = NULL; +#endif + + if (! strcmp (type, "text")) + { + cerror_if_not (nargs == 0, error_msg, "%s", "Too many arguments"); + return poppler_annot_text_new (doc->pdf, r); + } + +#ifdef HAVE_POPPLER_ANNOT_MARKUP + garray = g_array_new (FALSE, FALSE, sizeof (PopplerQuadrilateral)); + poppler_page_get_size (page, &width, &height); + for (i = 0; i < nargs; ++i) + { + PopplerRectangle *rr = &carg.value.rectangle; + + error_if_not (command_arg_parse_arg (ctx, args[i], &carg, + ARG_EDGES, error_msg)); + rr->x1 *= width; rr->x2 *= width; + rr->y1 *= height; rr->y2 *= height; + annotation_markup_append_text_region (page, rr, garray); + } + cerror_if_not (garray->len > 0, error_msg, "%s", + "Unable to create empty markup annotation"); + + if (! strcmp (type, "highlight")) + a = poppler_annot_text_markup_new_highlight (doc->pdf, r, garray); + else if (! strcmp (type, "squiggly")) + a = poppler_annot_text_markup_new_squiggly (doc->pdf, r, garray); + else if (! strcmp (type, "strike-out")) + a = poppler_annot_text_markup_new_strikeout (doc->pdf, r, garray); + else if (! strcmp (type, "underline")) + a = poppler_annot_text_markup_new_underline (doc->pdf, r, garray); + else + cerror_if_not (0, error_msg, "Unknown annotation type: %s", type); + +#endif + error: +#ifdef HAVE_POPPLER_ANNOT_MARKUP + if (garray) g_array_unref (garray); + if (region) cairo_region_destroy (region); +#endif + return a; +} + +static gboolean +annotation_edit_validate (const epdfinfo_t *ctx, const command_arg_t *rest, + PopplerAnnot *annotation, char **error_msg) +{ + int nargs = rest->value.rest.nargs; + char * const *args = rest->value.rest.args; + int i = 0; + command_arg_t carg; + + const char* error_fmt = + "Can modify `%s' property only for %s annotations"; + + while (i < nargs) + { + command_arg_type_t atype = ARG_INVALID; + const char *key = args[i++]; + + cerror_if_not (i < nargs, error_msg, "Missing a value argument"); + + if (! strcmp (key, "flags")) + atype = ARG_NATNUM; + else if (! strcmp (key, "color")) + atype = ARG_COLOR; + else if (! strcmp (key, "contents")) + atype = ARG_STRING; + else if (! strcmp (key, "edges")) + atype = ARG_EDGES_OR_POSITION; + else if (! strcmp (key, "label")) + { + cerror_if_not (POPPLER_IS_ANNOT_MARKUP (annotation), error_msg, + error_fmt, key, "markup"); + atype = ARG_STRING; + } + else if (! strcmp (key, "opacity")) + { + cerror_if_not (POPPLER_IS_ANNOT_MARKUP (annotation), error_msg, + error_fmt, key, "markup"); + atype = ARG_EDGE; + } + else if (! strcmp (key, "popup")) + { + cerror_if_not (POPPLER_IS_ANNOT_MARKUP (annotation), error_msg, + error_fmt, key, "markup"); + atype = ARG_EDGES; + } + else if (! strcmp (key, "popup-is-open")) + { + cerror_if_not (POPPLER_IS_ANNOT_MARKUP (annotation), error_msg, + error_fmt, key, "markup"); + atype = ARG_BOOL; + } + else if (! strcmp (key, "icon")) + { + cerror_if_not (POPPLER_IS_ANNOT_TEXT (annotation), error_msg, + error_fmt, key, "text"); + atype = ARG_STRING; + } + else if (! strcmp (key, "is-open")) + { + cerror_if_not (POPPLER_IS_ANNOT_TEXT (annotation), error_msg, + error_fmt, key, "text"); + atype = ARG_BOOL; + } + else + { + cerror_if_not (0, error_msg, + "Unable to modify property `%s'", key); + } + + if (! command_arg_parse_arg (ctx, args[i++], &carg, atype, error_msg)) + return FALSE; + } + + return TRUE; + + error: + return FALSE; +} + +static void +annotation_print (const annotation_t *annot, /* const */ PopplerPage *page) +{ + double width, height; + PopplerAnnotMapping *m; + const gchar *key; + PopplerAnnot *a; + PopplerAnnotMarkup *ma; + PopplerAnnotText *ta; + PopplerRectangle r; + PopplerColor *color; + gchar *text; + gdouble opacity; + cairo_region_t *region = NULL; + + if (! annot || ! page) + return; + + m = annot->amap; + key = annot->key; + a = m->annot; + poppler_page_get_size (page, &width, &height); + + r.x1 = m->area.x1; + r.x2 = m->area.x2; + r.y1 = height - m->area.y2; + r.y2 = height - m->area.y1; + +#ifdef HAVE_POPPLER_ANNOT_MARKUP + if (POPPLER_IS_ANNOT_TEXT_MARKUP (a)) + { + region = annotation_markup_get_text_regions (page, POPPLER_ANNOT_TEXT_MARKUP (a)); + perror_if_not (region, "%s", "Unable to extract annotation's text regions"); + } +#endif + + /* >>> Any Annotation >>> */ + /* Page */ + printf ("%d:", poppler_page_get_index (page) + 1); + /* Area */ + printf ("%f %f %f %f:", r.x1 / width, r.y1 / height + , r.x2 / width, r.y2 / height); + + /* Type */ + printf ("%s:", xpoppler_annot_type_string (poppler_annot_get_annot_type (a))); + /* Internal Key */ + print_response_string (key, COLON); + + /* Flags */ + printf ("%d:", poppler_annot_get_flags (a)); + + /* Color */ + color = poppler_annot_get_color (a); + if (color) + { + /* Reduce 2 Byte to 1 Byte color space */ + printf ("#%.2x%.2x%.2x", (color->red >> 8) + , (color->green >> 8) + , (color->blue >> 8)); + g_free (color); + } + + putchar (':'); + + /* Text Contents */ + text = poppler_annot_get_contents (a); + print_response_string (text, COLON); + g_free (text); + + /* Modified Date */ + text = poppler_annot_get_modified (a); + print_response_string (text, NONE); + g_free (text); + + /* <<< Any Annotation <<< */ + + /* >>> Markup Annotation >>> */ + if (! POPPLER_IS_ANNOT_MARKUP (a)) + { + putchar ('\n'); + goto theend; + } + + putchar (':'); + ma = POPPLER_ANNOT_MARKUP (a); + /* Label */ + text = poppler_annot_markup_get_label (ma); + print_response_string (text, COLON); + g_free (text); + + /* Subject */ + text = poppler_annot_markup_get_subject (ma); + print_response_string (text, COLON); + g_free (text); + + /* Opacity */ + opacity = poppler_annot_markup_get_opacity (ma); + printf ("%f:", opacity); + + /* Popup (Area + isOpen) */ + if (poppler_annot_markup_has_popup (ma) + && poppler_annot_markup_get_popup_rectangle (ma, &r)) + { + gdouble tmp = r.y1; + r.y1 = height - r.y2; + r.y2 = height - tmp; + printf ("%f %f %f %f:%d:", r.x1 / width, r.y1 / height + , r.x2 / width, r.y2 / height + , poppler_annot_markup_get_popup_is_open (ma) ? 1 : 0); + + } + else + printf ("::"); + + /* Creation Date */ + text = xpoppler_annot_markup_get_created (ma); + if (text) + { + print_response_string (text, NONE); + g_free (text); + } + + /* <<< Markup Annotation <<< */ + + /* >>> Text Annotation >>> */ + if (POPPLER_IS_ANNOT_TEXT (a)) + { + putchar (':'); + ta = POPPLER_ANNOT_TEXT (a); + /* Text Icon */ + text = poppler_annot_text_get_icon (ta); + print_response_string (text, COLON); + g_free (text); + /* Text State */ + printf ("%s:%d", + xpoppler_annot_text_state_string (poppler_annot_text_get_state (ta)), + poppler_annot_text_get_is_open (ta)); + } +#ifdef HAVE_POPPLER_ANNOT_MARKUP + /* <<< Text Annotation <<< */ + else if (POPPLER_IS_ANNOT_TEXT_MARKUP (a)) + { + /* >>> Markup Text Annotation >>> */ + putchar (':'); + region_print (region, width, height); + /* <<< Markup Text Annotation <<< */ + } +#endif + putchar ('\n'); + theend: +#ifdef HAVE_POPPLER_ANNOT_MARKUP + error: +#endif + if (region) cairo_region_destroy (region); +} + +static void +attachment_print (PopplerAttachment *att, const char *id, gboolean do_save) +{ + time_t time; + + print_response_string (id, COLON); + print_response_string (att->name, COLON); + print_response_string (att->description, COLON); + if (att->size + 1 != 0) + printf ("%" G_GSIZE_FORMAT ":", att->size); + else + printf ("-1:"); + time = (time_t) att->mtime; + print_response_string (time > 0 ? strchomp (ctime (&time)) : "", COLON); + time = (time_t) att->ctime; + print_response_string (time > 0 ? strchomp (ctime (&time)) : "", COLON); + print_response_string (att->checksum ? att->checksum->str : "" , COLON); + if (do_save) + { + char *filename = mktempfile (); + GError *error = NULL; + if (filename) + { + if (! poppler_attachment_save (att, filename, &error)) + { + fprintf (stderr, "Writing attachment failed: %s" + , error ? error->message : "reason unknown"); + if (error) + g_free (error); + } + else + { + print_response_string (filename, NONE); + } + free (filename); + } + } + putchar ('\n'); +} + + + +/* ================================================================== * + * Server command implementations + * ================================================================== */ + +/* Name: features + Args: None + Returns: A list of compile-time features. + Errors: None +*/ + +const command_arg_type_t cmd_features_spec[] = {}; + +static void +cmd_features (const epdfinfo_t *ctx, const command_arg_t *args) +{ + const char *features[] = { +#ifdef HAVE_POPPLER_FIND_OPTS + "case-sensitive-search", +#else + "no-case-sensitive-search", +#endif +#ifdef HAVE_POPPLER_ANNOT_WRITE + "writable-annotations", +#else + "no-writable-annotations", +#endif +#ifdef HAVE_POPPLER_ANNOT_MARKUP + "markup-annotations" +#else + "no-markup-annotations" +#endif + }; + int i; + OK_BEGIN (); + for (i = 0; i < G_N_ELEMENTS (features); ++i) + { + printf ("%s", features[i]); + if (i < G_N_ELEMENTS (features) - 1) + putchar (':'); + } + putchar ('\n'); + OK_END (); +} + + +/* Name: open + Args: filename password + Returns: Nothing + Errors: If file can't be opened or is not a PDF document. +*/ + +const command_arg_type_t cmd_open_spec[] = + { + ARG_NONEMPTY_STRING, /* filename */ + ARG_STRING, /* password */ + }; + +static void +cmd_open (const epdfinfo_t *ctx, const command_arg_t *args) +{ + const char *filename = args[0].value.string; + const char *passwd = args[1].value.string; + GError *gerror = NULL; + document_t *doc; + + if (! *passwd) + passwd = NULL; + + doc = document_open(ctx, filename, passwd, &gerror); + perror_if_not (doc, "Error opening %s:%s", filename, + gerror ? gerror->message : "unknown error"); + OK (); + + error: + if (gerror) + { + g_error_free (gerror); + gerror = NULL; + } +} + +/* Name: close + Args: filename + Returns: 1 if file was open, otherwise 0. + Errors: None +*/ + +const command_arg_type_t cmd_close_spec[] = + { + ARG_NONEMPTY_STRING /* filename */ + }; + +static void +cmd_close (const epdfinfo_t *ctx, const command_arg_t *args) +{ + document_t *doc = g_hash_table_lookup(ctx->documents, args->value.string); + + g_hash_table_remove (ctx->documents, args->value.string); + free_document (doc); + OK_BEGIN (); + puts (doc ? "1" : "0"); + OK_END (); +} + +/* Name: closeall + Args: None + Returns: Nothing + Errors: None +*/ +static void +cmd_closeall (const epdfinfo_t *ctx, const command_arg_t *args) +{ + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, ctx->documents); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + document_t *doc = (document_t*) value; + free_document (doc); + g_hash_table_iter_remove (&iter); + } + OK (); +} + + +const command_arg_type_t cmd_search_regexp_spec[] = + { + ARG_DOC, + ARG_NATNUM, /* first page */ + ARG_NATNUM, /* last page */ + ARG_NONEMPTY_STRING, /* regexp */ + ARG_NATNUM, /* compile flags */ + ARG_NATNUM /* match flags */ + }; + +static void +cmd_search_regexp(const epdfinfo_t *ctx, const command_arg_t *args) +{ + PopplerDocument *doc = args[0].value.doc->pdf; + int first = args[1].value.natnum; + int last = args[2].value.natnum; + const gchar *regexp = args[3].value.string; + GRegexCompileFlags cflags = args[4].value.natnum; + GRegexMatchFlags mflags = args[5].value.natnum; + double width, height; + int pn; + GError *gerror = NULL; + GRegex *re = NULL; + + NORMALIZE_PAGE_ARG (doc, &first, &last); + + re = g_regex_new (regexp, cflags, mflags, &gerror); + perror_if_not (NULL == gerror, "Invalid regexp: %s", gerror->message); + + OK_BEGIN (); + for (pn = first; pn <= last; ++pn) + { + PopplerPage *page = poppler_document_get_page(doc, pn - 1); + char *text; + PopplerRectangle *rectangles = NULL; + guint nrectangles; + GMatchInfo *match = NULL; + + if (! page) + continue; + + text = poppler_page_get_text (page); + poppler_page_get_text_layout (page, &rectangles, &nrectangles); + poppler_page_get_size (page, &width, &height); + g_regex_match (re, text, 0, &match); + + while (g_match_info_matches (match)) + { + const double scale = 100.0; + gint start, end, ustart, ulen; + gchar *string = NULL; + gchar *line = NULL; + int i; + + /* Does this ever happen ? */ + if (! g_match_info_fetch_pos (match, 0, &start, &end)) + continue; + + string = g_match_info_fetch (match, 0); + ustart = g_utf8_strlen (text, start); + ulen = g_utf8_strlen (string, -1); + + cairo_region_t *region = cairo_region_create (); + /* Merge matched glyph rectangles. Scale them so we're able + to use cairo . */ + if (ulen > 0) + { + assert (ustart < nrectangles + && ustart + ulen <= nrectangles); + line = poppler_page_get_selected_text + (page, POPPLER_SELECTION_LINE, rectangles + ustart); + + for (i = ustart; i < ustart + ulen; ++i) + { + PopplerRectangle *r = rectangles + i; + cairo_rectangle_int_t c; + + c.x = (int) (scale * r->x1 + 0.5); + c.y = (int) (scale * r->y1 + 0.5); + c.width = (int) (scale * (r->x2 - r->x1) + 0.5); + c.height = (int) (scale * (r->y2 - r->y1) + 0.5); + + cairo_region_union_rectangle (region, &c); + } + + } + + printf ("%d:", pn); + print_response_string (string, COLON); + print_response_string (strchomp (line), COLON); + region_print (region, width * scale, height * scale); + putchar ('\n'); + cairo_region_destroy (region); + g_free (string); + g_free (line); + g_match_info_next (match, NULL); + } + g_free (rectangles); + g_object_unref (page); + g_free (text); + g_match_info_free (match); + } + OK_END (); + + error: + if (re) g_regex_unref (re); + if (gerror) g_error_free (gerror); +} + +const command_arg_type_t cmd_regexp_flags_spec[] = + { + }; + +static void +cmd_regexp_flags (const epdfinfo_t *ctx, const command_arg_t *args) +{ + OK_BEGIN (); + printf ("caseless:%d\n", G_REGEX_CASELESS); + printf ("multiline:%d\n", G_REGEX_MULTILINE); + printf ("dotall:%d\n", G_REGEX_DOTALL); + printf ("extended:%d\n", G_REGEX_EXTENDED); + printf ("anchored:%d\n", G_REGEX_ANCHORED); + printf ("dollar-endonly:%d\n", G_REGEX_DOLLAR_ENDONLY); + printf ("ungreedy:%d\n", G_REGEX_UNGREEDY); + printf ("raw:%d\n", G_REGEX_RAW); + printf ("no-auto-capture:%d\n", G_REGEX_NO_AUTO_CAPTURE); + printf ("optimize:%d\n", G_REGEX_OPTIMIZE); + printf ("dupnames:%d\n", G_REGEX_DUPNAMES); + printf ("newline-cr:%d\n", G_REGEX_NEWLINE_CR); + printf ("newline-lf:%d\n", G_REGEX_NEWLINE_LF); + printf ("newline-crlf:%d\n", G_REGEX_NEWLINE_CRLF); + + printf ("match-anchored:%d\n", G_REGEX_MATCH_ANCHORED); + printf ("match-notbol:%d\n", G_REGEX_MATCH_NOTBOL); + printf ("match-noteol:%d\n", G_REGEX_MATCH_NOTEOL); + printf ("match-notempty:%d\n", G_REGEX_MATCH_NOTEMPTY); + printf ("match-partial:%d\n", G_REGEX_MATCH_PARTIAL); + printf ("match-newline-cr:%d\n", G_REGEX_MATCH_NEWLINE_CR); + printf ("match-newline-lf:%d\n", G_REGEX_MATCH_NEWLINE_LF); + printf ("match-newline-crlf:%d\n", G_REGEX_MATCH_NEWLINE_CRLF); + printf ("match-newline-any:%d\n", G_REGEX_MATCH_NEWLINE_ANY); + + OK_END (); +} + + +const command_arg_type_t cmd_search_string_spec[] = + { + ARG_DOC, + ARG_NATNUM, /* first page */ + ARG_NATNUM, /* last page */ + ARG_NONEMPTY_STRING, /* search string */ + ARG_BOOL, /* ignore-case */ + }; + +static void +cmd_search_string(const epdfinfo_t *ctx, const command_arg_t *args) +{ + PopplerDocument *doc = args[0].value.doc->pdf; + int first = args[1].value.natnum; + int last = args[2].value.natnum; + const char *string = args[3].value.string; + gboolean ignore_case = args[4].value.flag; + GList *list, *item; + double width, height; + int pn; +#ifdef HAVE_POPPLER_FIND_OPTS + PopplerFindFlags flags = ignore_case ? 0 : POPPLER_FIND_CASE_SENSITIVE; +#endif + + NORMALIZE_PAGE_ARG (doc, &first, &last); + OK_BEGIN (); + for (pn = first; pn <= last; ++pn) + { + PopplerPage *page = poppler_document_get_page(doc, pn - 1); + + if (! page) + continue; + +#ifdef HAVE_POPPLER_FIND_OPTS + list = poppler_page_find_text_with_options(page, string, flags); +#else + list = poppler_page_find_text(page, string); +#endif + + poppler_page_get_size (page, &width, &height); + + for (item = list; item; item = item->next) + { + gchar *line, *match; + PopplerRectangle *r = item->data; + gdouble y1 = r->y1; + + r->y1 = height - r->y2; + r->y2 = height - y1; + + printf ("%d:", pn); + line = strchomp (poppler_page_get_selected_text + (page, POPPLER_SELECTION_LINE, r)); + match = strchomp (poppler_page_get_selected_text + (page, POPPLER_SELECTION_GLYPH, r)); + print_response_string (match, COLON); + print_response_string (line, COLON); + printf ("%f %f %f %f\n", + r->x1 / width, r->y1 / height, + r->x2 / width, r->y2 / height); + g_free (line); + g_free (match); + poppler_rectangle_free (r); + } + g_list_free (list); + g_object_unref (page); + } + OK_END (); +} + +/* Name: metadata + Args: filename + Returns: PDF's metadata + Errors: None + + title author subject keywords creator producer pdf-version create-date mod-date + + Dates are in seconds since the epoche. + +*/ + +const command_arg_type_t cmd_metadata_spec[] = + { + ARG_DOC, + }; + +static void +cmd_metadata (const epdfinfo_t *ctx, const command_arg_t *args) +{ + PopplerDocument *doc = args[0].value.doc->pdf; + time_t date; + gchar *md[6]; + gchar *title; + int i; + char *time_str; + + OK_BEGIN (); + + title = poppler_document_get_title (doc); + print_response_string (title, COLON); + g_free (title); + + md[0] = poppler_document_get_author (doc); + md[1] = poppler_document_get_subject (doc); + md[2] = poppler_document_get_keywords (doc); + md[3] = poppler_document_get_creator (doc); + md[4] = poppler_document_get_producer (doc); + md[5] = poppler_document_get_pdf_version_string (doc); + + for (i = 0; i < 6; ++i) + { + print_response_string (md[i], COLON); + g_free (md[i]); + } + + date = poppler_document_get_creation_date (doc); + time_str = strchomp (ctime (&date)); + print_response_string (time_str ? time_str : "", COLON); + date = poppler_document_get_modification_date (doc); + time_str = strchomp (ctime (&date)); + print_response_string (time_str ? time_str : "", NEWLINE); + OK_END (); +} + +/* Name: outline + Args: filename + + Returns: The documents outline (or index) as a, possibly empty, + list of records: + + tree-level ACTION + + See cmd_pagelinks for how ACTION is constructed. + + Errors: None +*/ + +static void +cmd_outline_walk (PopplerDocument *doc, PopplerIndexIter *iter, int depth) +{ + do + { + PopplerIndexIter *child; + PopplerAction *action = poppler_index_iter_get_action (iter); + + if (! action) + continue; + + if (action_is_handled (action)) + { + printf ("%d:", depth); + action_print (doc, action); + } + + child = poppler_index_iter_get_child (iter); + if (child) + { + cmd_outline_walk (doc, child, depth + 1); + } + poppler_action_free (action); + poppler_index_iter_free (child); + } while (poppler_index_iter_next (iter)); +} + +const command_arg_type_t cmd_outline_spec[] = + { + ARG_DOC, + }; + +static void +cmd_outline (const epdfinfo_t *ctx, const command_arg_t *args) +{ + PopplerIndexIter *iter = poppler_index_iter_new (args->value.doc->pdf); + OK_BEGIN (); + if (iter) + { + cmd_outline_walk (args->value.doc->pdf, iter, 1); + poppler_index_iter_free (iter); + } + OK_END (); +} + +/* Name: quit + Args: None + Returns: Nothing + Errors: None + + Close all documents and exit. +*/ + + +const command_arg_type_t cmd_quit_spec[] = {}; + +static void +cmd_quit (const epdfinfo_t *ctx, const command_arg_t *args) +{ + cmd_closeall (ctx, args); + exit (EXIT_SUCCESS); +} + +/* Name: number-of-pages + Args: filename + Returns: The number of pages. + Errors: None +*/ + + +const command_arg_type_t cmd_number_of_pages_spec[] = + { + ARG_DOC + }; + +static void +cmd_number_of_pages (const epdfinfo_t *ctx, const command_arg_t *args) +{ + int npages = poppler_document_get_n_pages (args->value.doc->pdf); + OK_BEGIN (); + printf ("%d\n", npages); + OK_END (); +} + +/* Name: pagelinks + Args: filename page + Returns: A list of linkmaps: + + edges ACTION , + + where ACTION is one of + + 'goto-dest' title page top + 'goto-remote' title filename page top + 'uri' title URI + 'launch' title program arguments + + top is desired vertical position, filename is the target PDF of the + `goto-remote' link. + + Errors: None +*/ + + +const command_arg_type_t cmd_pagelinks_spec[] = + { + ARG_DOC, + ARG_NATNUM /* page number */ + }; + +static void +cmd_pagelinks(const epdfinfo_t *ctx, const command_arg_t *args) +{ + PopplerDocument *doc = args[0].value.doc->pdf; + PopplerPage *page = NULL; + int pn = args[1].value.natnum; + double width, height; + GList *link_map = NULL, *item; + + page = poppler_document_get_page (doc, pn - 1); + perror_if_not (page, "No such page %d", pn); + poppler_page_get_size (page, &width, &height); + link_map = poppler_page_get_link_mapping (page); + + OK_BEGIN (); + for (item = g_list_last (link_map); item; item = item->prev) + { + + PopplerLinkMapping *link = item->data; + PopplerRectangle *r = &link->area; + gdouble y1 = r->y1; + /* LinkMappings have a different gravity. */ + r->y1 = height - r->y2; + r->y2 = height - y1; + + if (! action_is_handled (link->action)) + continue; + + printf ("%f %f %f %f:", + r->x1 / width, r->y1 / height, + r->x2 / width, r->y2 / height); + action_print (doc, link->action); + } + OK_END (); + error: + if (page) g_object_unref (page); + if (link_map) poppler_page_free_link_mapping (link_map); +} + +/* Name: gettext + Args: filename page edges selection-style + Returns: The selection's text. + Errors: If page is out of range. + + For the selection-style argument see getselection command. +*/ + + +const command_arg_type_t cmd_gettext_spec[] = + { + ARG_DOC, + ARG_NATNUM, /* page number */ + ARG_EDGES, /* selection */ + ARG_NATNUM /* selection-style */ + }; + +static void +cmd_gettext(const epdfinfo_t *ctx, const command_arg_t *args) +{ + PopplerDocument *doc = args[0].value.doc->pdf; + int pn = args[1].value.natnum; + PopplerRectangle r = args[2].value.rectangle; + int selection_style = args[3].value.natnum; + PopplerPage *page = NULL; + double width, height; + gchar *text = NULL; + + switch (selection_style) + { + case POPPLER_SELECTION_GLYPH: break; + case POPPLER_SELECTION_LINE: break; + case POPPLER_SELECTION_WORD: break; + default: selection_style = POPPLER_SELECTION_GLYPH; + } + + page = poppler_document_get_page (doc, pn - 1); + perror_if_not (page, "No such page %d", pn); + poppler_page_get_size (page, &width, &height); + r.x1 = r.x1 * width; + r.x2 = r.x2 * width; + r.y1 = r.y1 * height; + r.y2 = r.y2 * height; + /* printf ("%f %f %f %f , %f %f\n", r.x1, r.y1, r.x2, r.y2, width, height); */ + text = poppler_page_get_selected_text (page, selection_style, &r); + + OK_BEGIN (); + print_response_string (text, NEWLINE); + OK_END (); + + error: + g_free (text); + if (page) g_object_unref (page); +} + +/* Name: getselection + Args: filename page edges selection-selection_style + Returns: The selection's text. + Errors: If page is out of range. + + selection-selection_style should be as follows. + + 0 (POPPLER_SELECTION_GLYPH) + glyph is the minimum unit for selection + + 1 (POPPLER_SELECTION_WORD) + word is the minimum unit for selection + + 2 (POPPLER_SELECTION_LINE) + line is the minimum unit for selection +*/ + + +const command_arg_type_t cmd_getselection_spec[] = + { + ARG_DOC, + ARG_NATNUM, /* page number */ + ARG_EDGES, /* selection */ + ARG_NATNUM /* selection-style */ + }; + +static void +cmd_getselection (const epdfinfo_t *ctx, const command_arg_t *args) +{ + PopplerDocument *doc = args[0].value.doc->pdf; + int pn = args[1].value.natnum; + PopplerRectangle r = args[2].value.rectangle; + int selection_style = args[3].value.natnum; + gdouble width, height; + cairo_region_t *region = NULL; + PopplerPage *page = NULL; + int i; + + switch (selection_style) + { + case POPPLER_SELECTION_GLYPH: break; + case POPPLER_SELECTION_LINE: break; + case POPPLER_SELECTION_WORD: break; + default: selection_style = POPPLER_SELECTION_GLYPH; + } + + page = poppler_document_get_page (doc, pn - 1); + perror_if_not (page, "No such page %d", pn); + poppler_page_get_size (page, &width, &height); + + r.x1 = r.x1 * width; + r.x2 = r.x2 * width; + r.y1 = r.y1 * height; + r.y2 = r.y2 * height; + + region = poppler_page_get_selected_region (page, 1.0, selection_style, &r); + + OK_BEGIN (); + for (i = 0; i < cairo_region_num_rectangles (region); ++i) + { + cairo_rectangle_int_t r; + + cairo_region_get_rectangle (region, i, &r); + printf ("%f %f %f %f\n", + r.x / width, + r.y / height, + (r.x + r.width) / width, + (r.y + r.height) / height); + } + OK_END (); + + error: + if (region) cairo_region_destroy (region); + if (page) g_object_unref (page); +} + +/* Name: pagesize + Args: filename page + Returns: width height + Errors: If page is out of range. +*/ + + +const command_arg_type_t cmd_pagesize_spec[] = + { + ARG_DOC, + ARG_NATNUM /* page number */ + }; + +static void +cmd_pagesize(const epdfinfo_t *ctx, const command_arg_t *args) +{ + PopplerDocument *doc = args[0].value.doc->pdf; + int pn = args[1].value.natnum; + PopplerPage *page = NULL; + double width, height; + + + page = poppler_document_get_page (doc, pn - 1); + perror_if_not (page, "No such page %d", pn); + poppler_page_get_size (page, &width, &height); + + OK_BEGIN (); + printf ("%f:%f\n", width, height); + OK_END (); + + error: + if (page) g_object_unref (page); +} + +/* Annotations */ + +/* Name: getannots + Args: filename firstpage lastpage + Returns: The list of annotations of this page. + + For all annotations + + page edges type key flags color contents mod-date + + ,where + + name is a document-unique name, + flags is PopplerAnnotFlag bitmask, + color is 3-byte RGB hex number and + + Then + + label subject opacity popup-edges popup-is-open create-date + + if this is a markup annotation and additionally + + text-icon text-state + + for markup text annotations. + + Errors: If page is out of range. +*/ + + +const command_arg_type_t cmd_getannots_spec[] = + { + ARG_DOC, + ARG_NATNUM, /* first page */ + ARG_NATNUM /* last page */ + }; + +static void +cmd_getannots(const epdfinfo_t *ctx, const command_arg_t *args) +{ + PopplerDocument *doc = args[0].value.doc->pdf; + gint first = args[1].value.natnum; + gint last = args[2].value.natnum; + GList *list; + gint pn; + + first = MAX(1, first); + if (last <= 0) + last = poppler_document_get_n_pages (doc); + else + last = MIN(last, poppler_document_get_n_pages (doc)); + + OK_BEGIN (); + for (pn = first; pn <= last; ++pn) + { + GList *annots = annoation_get_for_page (args->value.doc, pn); + PopplerPage *page = poppler_document_get_page (doc, pn - 1); + + if (! page) + continue; + + for (list = annots; list; list = list->next) + { + annotation_t *annot = (annotation_t *)list->data; + annotation_print (annot, page); + } + g_object_unref (page); + } + OK_END (); +} + +/* Name: getannot + Args: filename name + Returns: The annotation for name, see cmd_getannots. + Errors: If no annotation named ,name' exists. +*/ + + +const command_arg_type_t cmd_getannot_spec[] = + { + ARG_DOC, + ARG_NONEMPTY_STRING, /* annotation's key */ + }; + +static void +cmd_getannot (const epdfinfo_t *ctx, const command_arg_t *args) +{ + document_t *doc = args->value.doc; + const gchar *key = args[1].value.string; + PopplerPage *page = NULL; + annotation_t *a = annotation_get_by_key (doc, key); + gint index; + + perror_if_not (a, "No such annotation: %s", key); + index = poppler_annot_get_page_index (a->amap->annot); + if (index >= 0) + page = poppler_document_get_page (doc->pdf, index); + perror_if_not (page, "Unable to get page %d", index + 1); + + OK_BEGIN (); + annotation_print (a, page); + OK_END (); + + error: + if (page) g_object_unref (page); +} + +/* Name: getannot_attachment + Args: filename name [output-filename] + Returns: name description size mtime ctime output-filename + Errors: If no annotation named ,name' exists or output-filename is + not writable. +*/ + + +const command_arg_type_t cmd_getattachment_from_annot_spec[] = + { + ARG_DOC, + ARG_NONEMPTY_STRING, /* annotation's name */ + ARG_BOOL /* save attachment */ + }; + +static void +cmd_getattachment_from_annot (const epdfinfo_t *ctx, const command_arg_t *args) +{ + document_t *doc = args->value.doc; + const gchar *key = args[1].value.string; + gboolean do_save = args[2].value.flag; + PopplerAttachment *att = NULL; + annotation_t *a = annotation_get_by_key (doc, key); + gchar *id = NULL; + + perror_if_not (a, "No such annotation: %s", key); + perror_if_not (POPPLER_IS_ANNOT_FILE_ATTACHMENT (a->amap->annot), + "Not a file annotation: %s", key); + att = poppler_annot_file_attachment_get_attachment + (POPPLER_ANNOT_FILE_ATTACHMENT (a->amap->annot)); + perror_if_not (att, "Unable to get attachment: %s", key); + id = g_strdup_printf ("attachment-%s", key); + + OK_BEGIN (); + attachment_print (att, id, do_save); + OK_END (); + + error: + if (att) g_object_unref (att); + if (id) g_free (id); +} + + +/* document-level attachments */ +const command_arg_type_t cmd_getattachments_spec[] = + { + ARG_DOC, + ARG_BOOL, /* save attachments */ + }; + +static void +cmd_getattachments (const epdfinfo_t *ctx, const command_arg_t *args) +{ + document_t *doc = args->value.doc; + gboolean do_save = args[1].value.flag; + GList *item; + GList *attmnts = poppler_document_get_attachments (doc->pdf); + int i; + + OK_BEGIN (); + for (item = attmnts, i = 0; item; item = item->next, ++i) + { + PopplerAttachment *att = (PopplerAttachment*) item->data; + gchar *id = g_strdup_printf ("attachment-document-%d", i); + + attachment_print (att, id, do_save); + g_object_unref (att); + g_free (id); + } + g_list_free (attmnts); + + OK_END (); +} + +#ifdef HAVE_POPPLER_ANNOT_WRITE + +const command_arg_type_t cmd_addannot_spec[] = + { + ARG_DOC, + ARG_NATNUM, /* page number */ + ARG_STRING, /* type */ + ARG_EDGES_OR_POSITION, /* edges or position (uses default size) */ + ARG_REST, /* markup regions */ + }; + +static void +cmd_addannot (const epdfinfo_t *ctx, const command_arg_t *args) +{ + + document_t *doc = args->value.doc; + gint pn = args[1].value.natnum; + const char *type_string = args[2].value.string; + PopplerRectangle r = args[3].value.rectangle; + int i; + PopplerPage *page = NULL; + double width, height; + PopplerAnnot *pa; + PopplerAnnotMapping *amap; + annotation_t *a; + gchar *key; + GList *annotations; + gdouble y2; + char *error_msg = NULL; + + page = poppler_document_get_page (doc->pdf, pn - 1); + perror_if_not (page, "Unable to get page %d", pn); + poppler_page_get_size (page, &width, &height); + r.x1 *= width; r.x2 *= width; + r.y1 *= height; r.y2 *= height; + if (r.y2 < 0) + r.y2 = r.y1 + 24; + if (r.x2 < 0) + r.x2 = r.x1 + 24; + y2 = r.y2; + r.y2 = height - r.y1; + r.y1 = height - y2; + + pa = annotation_new (ctx, doc, page, type_string, &r, &args[4], &error_msg); + perror_if_not (pa, "Creating annotation failed: %s", + error_msg ? error_msg : "Reason unknown"); + amap = poppler_annot_mapping_new (); + amap->area = r; + amap->annot = pa; + annotations = annoation_get_for_page (doc, pn); + + i = g_list_length (annotations); + key = g_strdup_printf ("annot-%d-%d", pn, i); + while (g_hash_table_lookup (doc->annotations.keys, key)) + { + g_free (key); + key = g_strdup_printf ("annot-%d-%d", pn, ++i); + } + a = g_malloc (sizeof (annotation_t)); + a->amap = amap; + a->key = key; + doc->annotations.pages[pn - 1] = + g_list_prepend (annotations, a); + g_hash_table_insert (doc->annotations.keys, key, a); + poppler_page_add_annot (page, pa); + OK_BEGIN (); + annotation_print (a, page); + OK_END (); + + error: + if (page) g_object_unref (page); + if (error_msg) g_free (error_msg); +} + + +const command_arg_type_t cmd_delannot_spec[] = + { + ARG_DOC, + ARG_NONEMPTY_STRING /* Annotation's key */ + }; + +static void +cmd_delannot (const epdfinfo_t *ctx, const command_arg_t *args) +{ + document_t *doc = args->value.doc; + const gchar *key = args[1].value.string; + PopplerPage *page = NULL; + annotation_t *a = annotation_get_by_key (doc, key); + gint pn; + + perror_if_not (a, "No such annotation: %s", key); + pn = poppler_annot_get_page_index (a->amap->annot) + 1; + if (pn >= 1) + page = poppler_document_get_page (doc->pdf, pn - 1); + perror_if_not (page, "Unable to get page %d", pn); + poppler_page_remove_annot (page, a->amap->annot); + doc->annotations.pages[pn - 1] = + g_list_remove (doc->annotations.pages[pn - 1], a); + g_hash_table_remove (doc->annotations.keys, a->key); + poppler_annot_mapping_free(a->amap); + OK (); + + error: + if (a) + { + g_free (a->key); + g_free (a); + } + if (page) g_object_unref (page); +} + +const command_arg_type_t cmd_editannot_spec[] = + { + ARG_DOC, + ARG_NONEMPTY_STRING, /* annotation key */ + ARG_REST /* (KEY VALUE ...) */ + }; + +static void +cmd_editannot (const epdfinfo_t *ctx, const command_arg_t *args) +{ + document_t *doc = args->value.doc; + const char *key = args[1].value.string; + int nrest_args = args[2].value.rest.nargs; + char * const *rest_args = args[2].value.rest.args; + annotation_t *a = annotation_get_by_key (doc, key); + PopplerAnnot *pa; + PopplerPage *page = NULL; + int i = 0; + gint index; + char *error_msg = NULL; + command_arg_t carg; + const char *unexpected_parse_error = "Internal error while parsing arg `%s'"; + + perror_if_not (a, "No such annotation: %s", key); + pa = a->amap->annot; + perror_if_not (annotation_edit_validate (ctx, &args[2], pa, &error_msg), + "%s", error_msg); + index = poppler_annot_get_page_index (pa); + page = poppler_document_get_page (doc->pdf, index); + perror_if_not (page, "Unable to get page %d for annotation", index); + + for (i = 0; i < nrest_args; ++i) + { + const char *key = rest_args[i++]; + + if (! strcmp (key, "flags")) + { + perror_if_not (command_arg_parse_arg (ctx, rest_args[i], &carg, + ARG_NATNUM, NULL), + unexpected_parse_error, rest_args[i]); + poppler_annot_set_flags (pa, carg.value.natnum); + } + else if (! strcmp (key, "color")) + { + perror_if_not (command_arg_parse_arg (ctx, rest_args[i], &carg, + ARG_COLOR, NULL), + unexpected_parse_error, rest_args[i]); + poppler_annot_set_color (pa, &carg.value.color); + } + else if (! strcmp (key, "contents")) + { + perror_if_not (command_arg_parse_arg (ctx, rest_args[i], &carg, + ARG_STRING, NULL), + unexpected_parse_error, rest_args[i]); + poppler_annot_set_contents (pa, carg.value.string); + } + else if (! strcmp (key, "edges")) + { + PopplerRectangle *area = &a->amap->area; + gdouble width, height; + PopplerRectangle r; + + perror_if_not (command_arg_parse_arg (ctx, rest_args[i], &carg, + ARG_EDGES_OR_POSITION, NULL), + unexpected_parse_error, rest_args[i]); + r = carg.value.rectangle; + poppler_page_get_size (page, &width, &height); + + /* Translate Gravity and maybe keep the width and height. */ + if (r.x2 < 0) + area->x2 += (r.x1 * width) - area->x1; + else + area->x2 = r.x2 * width; + + if (r.y2 < 0) + area->y1 -= (r.y1 * height) - (height - area->y2); + else + area->y1 = height - (r.y2 * height); + + area->x1 = r.x1 * width; + area->y2 = height - (r.y1 * height); + + xpoppler_annot_set_rectangle (pa, area); + } + else if (! strcmp (key, "label")) + { + PopplerAnnotMarkup *ma = POPPLER_ANNOT_MARKUP (pa); + perror_if_not (command_arg_parse_arg (ctx, rest_args[i], &carg, + ARG_STRING, NULL), + unexpected_parse_error, rest_args[i]); + poppler_annot_markup_set_label (ma, carg.value.string); + } + else if (! strcmp (key, "opacity")) + { + PopplerAnnotMarkup *ma = POPPLER_ANNOT_MARKUP (pa); + perror_if_not (command_arg_parse_arg (ctx, rest_args[i], &carg, + ARG_EDGE, NULL), + unexpected_parse_error, rest_args[i]); + poppler_annot_markup_set_opacity (ma, carg.value.edge); + } + else if (! strcmp (key, "popup")) + { + PopplerAnnotMarkup *ma = POPPLER_ANNOT_MARKUP (pa); + perror_if_not (command_arg_parse_arg (ctx, rest_args[i], &carg, + ARG_EDGES, NULL), + unexpected_parse_error, rest_args[i]); + poppler_annot_markup_set_popup (ma, &carg.value.rectangle); + } + else if (! strcmp (key, "popup-is-open")) + { + PopplerAnnotMarkup *ma = POPPLER_ANNOT_MARKUP (pa); + perror_if_not (command_arg_parse_arg (ctx, rest_args[i], &carg, + ARG_BOOL, NULL), + unexpected_parse_error, rest_args[i]); + poppler_annot_markup_set_popup_is_open (ma, carg.value.flag); + } + else if (! strcmp (key, "icon")) + { + PopplerAnnotText *ta = POPPLER_ANNOT_TEXT (pa); + perror_if_not (command_arg_parse_arg (ctx, rest_args[i], &carg, + ARG_STRING, NULL), + unexpected_parse_error, rest_args[i]); + poppler_annot_text_set_icon (ta, carg.value.string); + } + else if (! strcmp (key, "is-open")) + { + PopplerAnnotText *ta = POPPLER_ANNOT_TEXT (pa); + perror_if_not (command_arg_parse_arg (ctx, rest_args[i], &carg, + ARG_BOOL, NULL), + unexpected_parse_error, rest_args[i]); + poppler_annot_text_set_is_open (ta, carg.value.flag); + } + else + { + perror_if_not (0, "internal error: annotation property validation failed"); + } + } + + OK_BEGIN (); + annotation_print (a, page); + OK_END (); + + error: + if (error_msg) g_free (error_msg); + if (page) g_object_unref (page); +} + +const command_arg_type_t cmd_save_spec[] = + { + ARG_DOC, + }; + +static void +cmd_save (const epdfinfo_t *ctx, const command_arg_t *args) +{ + document_t *doc = args->value.doc; + char *filename = mktempfile (); + GError *gerror = NULL; + gchar *uri; + gboolean success = FALSE; + + if (!filename) + { + printf_error_response ("Unable to create temporary file"); + return; + } + + uri = g_filename_to_uri (filename, NULL, &gerror); + + if (uri) + { + success = poppler_document_save (doc->pdf, uri, &gerror); + g_free (uri); + } + if (! success) + { + printf_error_response ("Error while saving %s:%s" + , filename, gerror ? gerror->message : "?"); + if (gerror) + g_error_free (gerror); + return; + } + OK_BEGIN (); + print_response_string (filename, NEWLINE); + OK_END (); +} + +#endif /* HAVE_POPPLER_ANNOT_WRITE */ + + +const command_arg_type_t cmd_synctex_forward_search_spec[] = + { + ARG_DOC, + ARG_NONEMPTY_STRING, /* source file */ + ARG_NATNUM, /* line number */ + ARG_NATNUM /* column number */ + }; + +static void +cmd_synctex_forward_search (const epdfinfo_t *ctx, const command_arg_t *args) +{ + document_t *doc = args[0].value.doc; + const char *source = args[1].value.string; + int line = args[2].value.natnum; + int column = args[3].value.natnum; + synctex_scanner_p scanner = NULL; + synctex_node_p node; + float x1, y1, x2, y2; + PopplerPage *page = NULL; + double width, height; + int pn; + + scanner = synctex_scanner_new_with_output_file (doc->filename, NULL, 1); + perror_if_not (scanner, "Unable to create synctex scanner,\ + did you run latex with `--synctex=1' ?"); + + perror_if_not (synctex_display_query (scanner, source, line, column, 0) + && (node = synctex_scanner_next_result (scanner)), + "Destination not found"); + + pn = synctex_node_page (node); + page = poppler_document_get_page(doc->pdf, pn - 1); + perror_if_not (page, "Page not found"); + x1 = synctex_node_box_visible_h (node); + y1 = synctex_node_box_visible_v (node) + - synctex_node_box_visible_height (node); + x2 = synctex_node_box_visible_width (node) + x1; + y2 = synctex_node_box_visible_depth (node) + + synctex_node_box_visible_height (node) + y1; + poppler_page_get_size (page, &width, &height); + x1 /= width; + y1 /= height; + x2 /= width; + y2 /= height; + + OK_BEGIN (); + printf("%d:%f:%f:%f:%f\n", pn, x1, y1, x2, y2); + OK_END (); + + error: + if (page) g_object_unref (page); + if (scanner) synctex_scanner_free (scanner); +} + + +const command_arg_type_t cmd_synctex_backward_search_spec[] = + { + ARG_DOC, + ARG_NATNUM, /* page number */ + ARG_EDGE, /* x */ + ARG_EDGE /* y */ + }; + +static void +cmd_synctex_backward_search (const epdfinfo_t *ctx, const command_arg_t *args) +{ + document_t *doc = args[0].value.doc; + int pn = args[1].value.natnum; + double x = args[2].value.edge; + double y = args[3].value.edge; + synctex_scanner_p scanner = NULL; + const char *filename; + PopplerPage *page = NULL; + synctex_node_p node; + double width, height; + int line, column; + + scanner = synctex_scanner_new_with_output_file (doc->filename, NULL, 1); + perror_if_not (scanner, "Unable to create synctex scanner,\ + did you run latex with `--synctex=1' ?"); + + page = poppler_document_get_page(doc->pdf, pn - 1); + perror_if_not (page, "Page not found"); + poppler_page_get_size (page, &width, &height); + x = x * width; + y = y * height; + + if (! synctex_edit_query (scanner, pn, x, y) + || ! (node = synctex_scanner_next_result (scanner)) + || ! (filename = + synctex_scanner_get_name (scanner, synctex_node_tag (node)))) + { + printf_error_response ("Destination not found"); + goto error; + } + + line = synctex_node_line (node); + column = synctex_node_column (node); + + OK_BEGIN (); + print_response_string (filename, COLON); + printf("%d:%d\n", line, column); + OK_END (); + + error: + if (page) g_object_unref (page); + if (scanner) synctex_scanner_free (scanner); +} + + +const command_arg_type_t cmd_renderpage_spec[] = + { + ARG_DOC, + ARG_NATNUM, /* page number */ + ARG_NATNUM, /* width */ + ARG_REST, /* commands */ + }; + +static void +cmd_renderpage (const epdfinfo_t *ctx, const command_arg_t *args) +{ + document_t *doc = args[0].value.doc; + int pn = args[1].value.natnum; + int width = args[2].value.natnum; + int nrest_args = args[3].value.rest.nargs; + char * const *rest_args = args[3].value.rest.args; + PopplerPage *page = poppler_document_get_page(doc->pdf, pn - 1); + cairo_surface_t *surface = NULL; + cairo_t *cr = NULL; + command_arg_t rest_arg; + gchar *error_msg = NULL; + double pt_width, pt_height; + PopplerColor fg = { 0, 0, 0 }; + PopplerColor bg = { 65535, 0, 0 }; + double alpha = 1.0; + double line_width = 1.5; + PopplerRectangle cb = {0.0, 0.0, 1.0, 1.0}; + int i = 0; + + perror_if_not (page, "No such page %d", pn); + poppler_page_get_size (page, &pt_width, &pt_height); + surface = image_render_page (doc->pdf, page, width, 1, + &doc->options.render); + perror_if_not (surface, "Failed to render page %d", pn); + + if (! nrest_args) + goto theend; + + cr = cairo_create (surface); + cairo_scale (cr, width / pt_width, width / pt_width); + + while (i < nrest_args) + { + const char* keyword; + + perror_if_not (command_arg_parse_arg (ctx, rest_args[i], &rest_arg, + ARG_STRING, &error_msg), + "%s", error_msg); + keyword = rest_arg.value.string; + ++i; + + perror_if_not (i < nrest_args, "Keyword is `%s' missing an argument", + keyword); + + if (! strcmp (keyword, ":foreground") + || ! strcmp (keyword, ":background")) + { + perror_if_not (command_arg_parse_arg (ctx, rest_args[i], &rest_arg, + ARG_COLOR, &error_msg), + "%s", error_msg); + ++i; + if (! strcmp (keyword, ":foreground")) + fg = rest_arg.value.color; + else + bg = rest_arg.value.color; + + } + else if (! strcmp (keyword, ":alpha")) + { + perror_if_not (command_arg_parse_arg (ctx, rest_args[i], &rest_arg, + ARG_EDGE, &error_msg), + "%s", error_msg); + ++i; + alpha = rest_arg.value.edge; + } + else if (! strcmp (keyword, ":crop-to") + || ! strcmp (keyword, ":highlight-region") + || ! strcmp (keyword, ":highlight-text") + || ! strcmp (keyword, ":highlight-line")) + { + PopplerRectangle *r; + perror_if_not (command_arg_parse_arg (ctx, rest_args[i], &rest_arg, + ARG_EDGES, &error_msg), + "%s", error_msg); + + ++i; + r = &rest_arg.value.rectangle; + + if (! strcmp (keyword, ":crop-to")) + { + gdouble w = (cb.x2 - cb.x1); + gdouble h = (cb.y2 - cb.y1); + gdouble x1 = cb.x1; + gdouble y1 = cb.y1; + + cb.x1 = r->x1 * w + x1; + cb.x2 = r->x2 * w + x1; + cb.y1 = r->y1 * h + y1; + cb.y2 = r->y2 * h + y1; + + } + else + { + r->x1 = pt_width * r->x1 * (cb.x2 - cb.x1) + pt_width * cb.x1; + r->x2 = pt_width * r->x2 * (cb.x2 - cb.x1) + pt_width * cb.x1; + r->y1 = pt_height * r->y1 * (cb.y2 - cb.y1) + pt_height * cb.y1; + r->y2 = pt_height * r->y2 * (cb.y2 - cb.y1) + pt_height * cb.y1; + + if (! strcmp (keyword, ":highlight-region")) + { + const double deg = M_PI / 180.0; + double rad; + + r->x1 += line_width / 2; + r->x2 -= line_width / 2; + r->y1 += line_width / 2; + r->y2 -= line_width / 2; + + rad = MIN (5, MIN (r->x2 - r->x1, r->y2 - r->y1) / 2.0); + + cairo_move_to (cr, r->x1 , r->y1 + rad); + cairo_arc (cr, r->x1 + rad, r->y1 + rad, rad, 180 * deg, 270 * deg); + cairo_arc (cr, r->x2 - rad, r->y1 + rad, rad, 270 * deg, 360 * deg); + cairo_arc (cr, r->x2 - rad, r->y2 - rad, rad, 0 * deg, 90 * deg); + cairo_arc (cr, r->x1 + rad, r->y2 - rad, rad, 90 * deg, 180 * deg); + cairo_close_path (cr); + + cairo_set_source_rgba (cr, + bg.red / 65535.0, + bg.green / 65535.0, + bg.blue / 65535.0, alpha); + cairo_fill_preserve (cr); + cairo_set_source_rgba (cr, + fg.red / 65535.0, + fg.green / 65535.0, + fg.blue / 65535.0, 1.0); + cairo_set_line_width (cr, line_width); + cairo_stroke (cr); + } + else + { + gboolean is_single_line = ! strcmp (keyword, ":highlight-line"); + + if (is_single_line) + { + gdouble m = r->y1 + (r->y2 - r->y1) / 2; + + /* Make the rectangle flat, otherwise poppler frequently + renders neighboring lines.*/ + r->y1 = m; + r->y2 = m; + } + + poppler_page_render_selection (page, cr, r, NULL, + POPPLER_SELECTION_GLYPH, &fg, &bg); + } + } + } + else + perror_if_not (0, "Unknown render command: %s", keyword); + } + if (cb.x1 != 0 || cb.y1 != 0 || cb.x2 != 1 || cb.y2 != 1) + { + int height = cairo_image_surface_get_height (surface); + cairo_rectangle_int_t r = {(int) (width * cb.x1 + 0.5), + (int) (height * cb.y1 + 0.5), + (int) (width * (cb.x2 - cb.x1) + 0.5), + (int) (height * (cb.y2 - cb.y1) + 0.5)}; + cairo_surface_t *nsurface = + cairo_image_surface_create (CAIRO_FORMAT_ARGB32, r.width, r.height); + perror_if_not (cairo_surface_status (surface) == CAIRO_STATUS_SUCCESS, + "%s", "Failed to create cairo surface"); + cairo_destroy (cr); + cr = cairo_create (nsurface); + perror_if_not (cairo_status (cr) == CAIRO_STATUS_SUCCESS, + "%s", "Failed to create cairo context"); + cairo_set_source_surface (cr, surface, -r.x, -r.y); + cairo_paint (cr); + cairo_surface_destroy (surface); + surface = nsurface; + } + + theend: + image_write_print_response (surface, PNG); + + error: + if (error_msg) g_free (error_msg); + if (cr) cairo_destroy (cr); + if (surface) cairo_surface_destroy (surface); + if (page) g_object_unref (page); +} + +const command_arg_type_t cmd_boundingbox_spec[] = + { + ARG_DOC, + ARG_NATNUM, /* page number */ + /* region */ + }; + +static void +cmd_boundingbox (const epdfinfo_t *ctx, const command_arg_t *args) +{ + document_t *doc = args[0].value.doc; + int pn = args[1].value.natnum; + PopplerPage *page = poppler_document_get_page(doc->pdf, pn - 1); + cairo_surface_t *surface = NULL; + int width, height; + double pt_width, pt_height; + unsigned char *data, *data_p; + PopplerRectangle bbox; + int i, j; + + perror_if_not (page, "No such page %d", pn); + poppler_page_get_size (page, &pt_width, &pt_height); + surface = image_render_page (doc->pdf, page, (int) pt_width, 1, + &doc->options.render); + + perror_if_not (cairo_surface_status(surface) == CAIRO_STATUS_SUCCESS, + "Failed to render page"); + + width = cairo_image_surface_get_width (surface); + height = cairo_image_surface_get_height (surface); + data = cairo_image_surface_get_data (surface); + + /* Determine the bbox by comparing each pixel in the 4 corner + stripes with the origin. */ + for (i = 0; i < width; ++i) + { + data_p = data + 4 * i; + for (j = 0; j < height; ++j, data_p += 4 * width) + { + if (! ARGB_EQUAL (data, data_p)) + break; + } + if (j < height) + break; + } + bbox.x1 = i; + + for (i = width - 1; i > -1; --i) + { + data_p = data + 4 * i; + for (j = 0; j < height; ++j, data_p += 4 * width) + { + if (! ARGB_EQUAL (data, data_p)) + break; + } + if (j < height) + break; + } + bbox.x2 = i + 1; + + for (i = 0; i < height; ++i) + { + data_p = data + 4 * i * width; + for (j = 0; j < width; ++j, data_p += 4) + { + if (! ARGB_EQUAL (data, data_p)) + break; + } + if (j < width) + break; + } + bbox.y1 = i; + + for (i = height - 1; i > -1; --i) + { + data_p = data + 4 * i * width; + for (j = 0; j < width; ++j, data_p += 4) + { + if (! ARGB_EQUAL (data, data_p)) + break; + } + if (j < width) + break; + } + bbox.y2 = i + 1; + + OK_BEGIN (); + if (bbox.x1 >= bbox.x2 || bbox.y1 >= bbox.y2) + { + /* empty page */ + puts ("0:0:1:1"); + } + else + { + printf ("%f:%f:%f:%f\n", + bbox.x1 / width, + bbox.y1 / height, + bbox.x2 / width, + bbox.y2 / height); + } + OK_END (); + + error: + if (surface) cairo_surface_destroy (surface); + if (page) g_object_unref (page); +} + +const command_arg_type_t cmd_charlayout_spec[] = + { + ARG_DOC, + ARG_NATNUM, /* page number */ + ARG_EDGES_OR_POSITION, /* region or position */ + }; + +static void +cmd_charlayout(const epdfinfo_t *ctx, const command_arg_t *args) +{ + PopplerDocument *doc = args[0].value.doc->pdf; + int pn = args[1].value.natnum; + PopplerRectangle region = args[2].value.rectangle; + double width, height; + PopplerPage *page = poppler_document_get_page(doc, pn - 1); + char *text = NULL; + char *text_p; + PopplerRectangle *rectangles = NULL; + guint nrectangles; + int i; + gboolean have_position = region.y2 < 0; + + perror_if_not (page, "No such page %d", pn); + + text = poppler_page_get_text (page); + text_p = text; + poppler_page_get_text_layout (page, &rectangles, &nrectangles); + poppler_page_get_size (page, &width, &height); + region.x1 *= width; + region.x2 *= width; + region.y1 *= height; + region.y2 *= height; + + OK_BEGIN (); + for (i = 0; i < nrectangles && *text_p; ++i) + { + PopplerRectangle *r = &rectangles[i]; + char *nextc = g_utf8_offset_to_pointer (text_p, 1); + + if ((have_position + && region.x1 >= r->x1 + && region.x1 <= r->x2 + && region.y1 >= r->y1 + && region.y1 <= r->y2) + || (! have_position + && r->x1 >= region.x1 + && r->y1 >= region.y1 + && r->x2 <= region.x2 + && r->y2 <= region.y2)) + { + char endc = *nextc; + + printf ("%f %f %f %f:", + r->x1 / width, r->y1 / height, + r->x2 / width, r->y2 / height); + *nextc = '\0'; + print_response_string (text_p, NEWLINE); + *nextc = endc; + } + text_p = nextc; + } + OK_END (); + + g_free (rectangles); + g_object_unref (page); + g_free (text); + + error: + return; +} + +const document_option_t document_options [] = + { + DEC_DOPT (":render/usecolors", ARG_BOOL, render.usecolors), + DEC_DOPT (":render/printed", ARG_BOOL, render.printed), + DEC_DOPT (":render/foreground", ARG_COLOR, render.fg), + DEC_DOPT (":render/background", ARG_COLOR, render.bg), + }; + +const command_arg_type_t cmd_getoptions_spec[] = + { + ARG_DOC, + }; + +static void +cmd_getoptions(const epdfinfo_t *ctx, const command_arg_t *args) +{ + document_t *doc = args[0].value.doc; + int i; + OK_BEGIN (); + for (i = 0; i < G_N_ELEMENTS (document_options); ++i) + { + command_arg_t arg; + + arg.type = document_options[i].type; + memcpy (&arg.value, + ((char*) &doc->options) + document_options[i].offset, + command_arg_type_size (arg.type)); + print_response_string (document_options[i].name, COLON); + command_arg_print (&arg); + puts(""); + } + OK_END (); +} + +const command_arg_type_t cmd_setoptions_spec[] = + { + ARG_DOC, + ARG_REST /* key value pairs */ + }; + +static void +cmd_setoptions(const epdfinfo_t *ctx, const command_arg_t *args) +{ + int i = 0; + document_t *doc = args[0].value.doc; + int nrest = args[1].value.rest.nargs; + char * const *rest = args[1].value.rest.args; + gchar *error_msg = NULL; + document_options_t opts = doc->options; + const size_t nopts = G_N_ELEMENTS (document_options); + + perror_if_not (nrest % 2 == 0, "Even number of key/value pairs expected"); + + while (i < nrest) + { + int j; + command_arg_t key, value; + + perror_if_not (command_arg_parse_arg + (ctx, rest[i], &key, ARG_NONEMPTY_STRING, &error_msg), + "%s", error_msg); + + ++i; + for (j = 0; j < nopts; ++j) + { + const document_option_t *dopt = &document_options[j]; + if (! strcmp (key.value.string, dopt->name)) + { + perror_if_not (command_arg_parse_arg + (ctx, rest[i], &value, dopt->type, &error_msg), + "%s", error_msg); + memcpy (((char*) &opts) + dopt->offset, + &value.value, command_arg_type_size (value.type)); + break; + } + } + perror_if_not (j < nopts, "Unknown option: %s", key.value.string); + ++i; + } + doc->options = opts; + cmd_getoptions (ctx, args); + + error: + if (error_msg) g_free (error_msg); +} + +const command_arg_type_t cmd_pagelabels_spec[] = + { + ARG_DOC, + }; + +static void +cmd_pagelabels(const epdfinfo_t *ctx, const command_arg_t *args) +{ + PopplerDocument *doc = args[0].value.doc->pdf; + int i; + + OK_BEGIN (); + for (i = 0; i < poppler_document_get_n_pages (doc); ++i) + { + PopplerPage *page = poppler_document_get_page(doc, i); + gchar *label = poppler_page_get_label (page); + + print_response_string (label ? label : "", NEWLINE); + g_object_unref (page); + g_free (label); + } + OK_END (); +} + +const command_arg_type_t cmd_ping_spec[] = + { + ARG_STRING /* any message */ + }; + +static void +cmd_ping (const epdfinfo_t *ctx, const command_arg_t *args) +{ + const gchar *msg = args[0].value.string; + OK_BEGIN (); + print_response_string (msg, NEWLINE); + OK_END (); +} + + +/* ================================================================== * + * Main + * ================================================================== */ + +static const command_t commands [] = + { + /* Basic */ + DEC_CMD (ping), + DEC_CMD (features), + DEC_CMD (open), + DEC_CMD (close), + DEC_CMD (quit), + DEC_CMD (getoptions), + DEC_CMD (setoptions), + + /* Searching */ + DEC_CMD2 (search_string, "search-string"), + DEC_CMD2 (search_regexp, "search-regexp"), + DEC_CMD2 (regexp_flags, "regexp-flags"), + + /* General Information */ + DEC_CMD (metadata), + DEC_CMD (outline), + DEC_CMD2 (number_of_pages, "number-of-pages"), + DEC_CMD (pagelinks), + DEC_CMD (gettext), + DEC_CMD (getselection), + DEC_CMD (pagesize), + DEC_CMD (boundingbox), + DEC_CMD (charlayout), + + /* General Information */ + DEC_CMD (metadata), + DEC_CMD (outline), + DEC_CMD2 (number_of_pages, "number-of-pages"), + DEC_CMD (pagelinks), + DEC_CMD (gettext), + DEC_CMD (getselection), + DEC_CMD (pagesize), + DEC_CMD (boundingbox), + DEC_CMD (charlayout), + DEC_CMD (pagelabels), + + /* Annotations */ + DEC_CMD (getannots), + DEC_CMD (getannot), +#ifdef HAVE_POPPLER_ANNOT_WRITE + DEC_CMD (addannot), + DEC_CMD (delannot), + DEC_CMD (editannot), + DEC_CMD (save), +#endif + + /* Attachments */ + DEC_CMD2 (getattachment_from_annot, "getattachment-from-annot"), + DEC_CMD (getattachments), + + /* Synctex */ + DEC_CMD2 (synctex_forward_search, "synctex-forward-search"), + DEC_CMD2 (synctex_backward_search, "synctex-backward-search"), + + /* Rendering */ + DEC_CMD (renderpage), + }; + +int main(int argc, char **argv) +{ + epdfinfo_t ctx = {0}; + char *line = NULL; + ssize_t read; + size_t line_size; + const char *error_log = "/dev/null"; + +#ifdef __MINGW32__ + error_log = "NUL"; + _setmode(_fileno(stdin), _O_BINARY); + _setmode(_fileno(stdout), _O_BINARY); +#endif + + if (argc > 2) + { + fprintf(stderr, "usage: epdfinfo [ERROR-LOGFILE]\n"); + exit (EXIT_FAILURE); + } + if (argc == 2) + error_log = argv[1]; + + if (! freopen (error_log, "a", stderr)) + err (2, "Unable to redirect stderr"); + +#if ! GLIB_CHECK_VERSION(2,36,0) + g_type_init (); +#endif + + ctx.documents = g_hash_table_new (g_str_hash, g_str_equal); + + setvbuf (stdout, NULL, _IOFBF, BUFSIZ); + + while ((read = getline (&line, &line_size, stdin)) != -1) + { + int nargs = 0; + command_arg_t *cmd_args = NULL; + char **args = NULL; + gchar *error_msg = NULL; + int i; + + if (read <= 1 || line[read - 1] != '\n') + { + fprintf (stderr, "Skipped parts of a line: `%s'\n", line); + goto next_line; + } + + line[read - 1] = '\0'; + args = command_arg_split (line, &nargs); + if (nargs == 0) + continue; + + for (i = 0; i < G_N_ELEMENTS (commands); i++) + { + if (! strcmp (commands[i].name, args[0])) + { + if (commands[i].nargs == 0 + || (cmd_args = command_arg_parse (&ctx, args + 1, nargs - 1, + commands + i, &error_msg))) + { + commands[i].execute (&ctx, cmd_args); + if (commands[i].nargs > 0) + free_command_args (cmd_args, commands[i].nargs); + } + else + { + printf_error_response ("%s", error_msg ? error_msg : + "Unknown error (this is a bug)"); + } + if (error_msg) + g_free (error_msg); + break; + } + } + if (G_N_ELEMENTS (commands) == i) + { + printf_error_response ("Unknown command: %s", args[0]); + } + for (i = 0; i < nargs; ++i) + g_free (args[i]); + g_free (args); + next_line: + free (line); + line = NULL; + } + + if (ferror (stdin)) + err (2, NULL); + exit (EXIT_SUCCESS); +} diff --git a/elpa/pdf-tools-20211110.513/build/server/epdfinfo.h b/elpa/pdf-tools-20211110.513/build/server/epdfinfo.h @@ -0,0 +1,252 @@ +// Copyright (C) 2013, 2014 Andreas Politz + +// Author: Andreas Politz <politza@fh-trier.de> + +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#ifndef _EPDF_H_ +#define _EPDF_H_ _EPDF_H_ +#include "config.h" +#include <glib.h> +#include <poppler.h> +#include <png.h> + +/* Some library functions print warnings to stdout, inhibit it. */ +#define DISCARD_STDOUT(saved_fd) \ + do { \ + int __fd; \ + fflush(stdout); \ + saved_fd = dup(1); \ + __fd = open("/dev/null", O_WRONLY); \ + dup2(__fd, 1); \ + close(__fd); \ + } while (0) + +#define UNDISCARD_STDOUT(saved_fd) \ + do { \ + fflush(stdout); \ + dup2(saved_fd, 1); \ + close(saved_fd); \ + } while (0) + +/* Writing responses */ +#define OK_BEGIN() \ + do { \ + puts("OK"); \ + } while (0) + +#define OK_END() \ + do { \ + puts("."); \ + fflush (stdout); \ + } while (0) + +#define OK() \ + do { \ + puts ("OK\n."); \ + fflush (stdout); \ + } while (0) + +/* Dealing with image data. */ +#ifdef WORDS_BIGENDIAN +#define ARGB_TO_RGB(rgb, argb) \ + do { \ + rgb[0] = argb[1]; \ + rgb[1] = argb[2]; \ + rgb[2] = argb[3]; \ + } while (0) + +#define ARGB_EQUAL(argb1, argb2) \ + (argb1[1] == argb2[1] \ + && argb1[2] == argb2[2] \ + && argb1[3] == argb2[3]) + +#else +#define ARGB_TO_RGB(rgb, argb) \ + do { \ + rgb[0] = argb[2]; \ + rgb[1] = argb[1]; \ + rgb[2] = argb[0]; \ + } while (0) + +#define ARGB_EQUAL(argb1, argb2) \ + (argb1[0] == argb2[0] \ + && argb1[1] == argb2[1] \ + && argb1[2] == argb2[2]) +#endif + +#define NORMALIZE_PAGE_ARG(doc, first, last) \ + *first = MAX(1, *first); \ + if (*last <= 0) \ + *last = poppler_document_get_n_pages (doc); \ + else \ + *last = MIN(*last, poppler_document_get_n_pages (doc)); + +/* png_jmpbuf is supposed to be not available in older versions of + libpng. */ +#ifndef png_jmpbuf +# define png_jmpbuf(png_ptr) ((png_ptr)->jmpbuf) +#endif + +#ifndef HAVE_ERROR_H +# define error(status, errno, fmt, args...) \ + do { \ + int error = (errno); \ + fflush (stdout); \ + fprintf (stderr, "%s: " fmt, PACKAGE_NAME, ## args); \ + if (error) \ + fprintf (stderr, ": %s", strerror (error)); \ + fprintf (stderr, "\n"); \ + exit (status); \ + } while (0) +#endif + +#define internal_error(fmt, args...) \ + error (2, 0, "internal error in %s: " fmt, __func__, ## args) + +#define error_if_not(expr) \ + if (! (expr)) goto error; + +#define perror_if_not(expr, fmt, args...) \ + do { \ + if (! (expr)) \ + { \ + printf_error_response ((fmt), ## args); \ + goto error; \ + } \ + } while (0) + +#define cerror_if_not(expr, error_msg, fmt, args...) \ + do { \ + if (! (expr)) \ + { \ + if (error_msg) \ + *(error_msg) = g_strdup_printf((fmt), ## args); \ + goto error; \ + } \ + } while (0) + +/* Declare commands */ +#define DEC_CMD(name) \ + {#name, cmd_ ## name, cmd_ ## name ## _spec, \ + G_N_ELEMENTS (cmd_ ## name ## _spec)} + +#define DEC_CMD2(command, name) \ + {name, cmd_ ## command, cmd_ ## command ## _spec, \ + G_N_ELEMENTS (cmd_ ## command ## _spec)} + +/* Declare option */ +#define DEC_DOPT(name, type, sname) \ + {name, type, offsetof (document_options_t, sname)} + +enum suffix_char { NONE, COLON, NEWLINE}; + +enum image_type { PPM, PNG }; + +typedef struct +{ + PopplerAnnotMapping *amap; + gchar *key; +} annotation_t; + +typedef enum + { + ARG_INVALID = 0, + ARG_DOC, + ARG_BOOL, + ARG_STRING, + ARG_NONEMPTY_STRING, + ARG_NATNUM, + ARG_EDGE, + ARG_EDGE_OR_NEGATIVE, + ARG_EDGES, + ARG_EDGES_OR_POSITION, + ARG_COLOR, + ARG_REST + } command_arg_type_t; + +typedef struct +{ + const char *name; + command_arg_type_t type; + size_t offset; +} document_option_t; + +typedef struct +{ + PopplerColor bg, fg; + gboolean usecolors; + gboolean printed; +} render_options_t; + +typedef struct +{ + render_options_t render; +} document_options_t; + +typedef struct +{ + PopplerDocument *pdf; + char *filename; + char *passwd; + struct + { + GHashTable *keys; /* key => page */ + GList **pages; /* page array */ + } annotations; + document_options_t options; +} document_t; + +typedef struct +{ + command_arg_type_t type; + union + { + gboolean flag; + const char *string; + long natnum; + document_t *doc; + gdouble edge; + PopplerColor color; + PopplerRectangle rectangle; +#ifdef HAVE_POPPLER_ANNOT_MARKUP + PopplerQuadrilateral quadrilateral; +#endif + struct + { + char * const *args; + int nargs; + } rest; + } value; +} command_arg_t; + +typedef struct +{ + GHashTable *documents; +} epdfinfo_t; + +typedef struct +{ + const char *name; + void (* execute) (const epdfinfo_t *ctxt, const command_arg_t *args); + const command_arg_type_t *args_spec; + int nargs; +} command_t; + +/* Defined in poppler-hack.cc */ +#ifdef HAVE_POPPLER_ANNOT_WRITE +extern void xpoppler_annot_set_rectangle (PopplerAnnot*, PopplerRectangle*); +#endif +extern gchar *xpoppler_annot_markup_get_created (PopplerAnnotMarkup*); +#endif /* _EPDF_H_ */ diff --git a/elpa/pdf-tools-20211110.513/build/server/install_test.cpp b/elpa/pdf-tools-20211110.513/build/server/install_test.cpp @@ -0,0 +1,7 @@ +#include <PDFDocEncoding.h> +#include <iostream> + +int main() { + std::cout << "Hello World, pdf-tools!"; + return 0; +} diff --git a/elpa/pdf-tools-20211110.513/build/server/m4/ax_check_compile_flag.m4 b/elpa/pdf-tools-20211110.513/build/server/m4/ax_check_compile_flag.m4 @@ -0,0 +1,74 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT]) +# +# DESCRIPTION +# +# Check whether the given FLAG works with the current language's compiler +# or gives an error. (Warnings, however, are ignored) +# +# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on +# success/failure. +# +# If EXTRA-FLAGS is defined, it is added to the current language's default +# flags (e.g. CFLAGS) when the check is done. The check is thus made with +# the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to +# force the compiler to issue an error when a bad flag is given. +# +# INPUT gives an alternative input source to AC_COMPILE_IFELSE. +# +# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this +# macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG. +# +# LICENSE +# +# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de> +# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com> +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see <https://www.gnu.org/licenses/>. +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 5 + +AC_DEFUN([AX_CHECK_COMPILE_FLAG], +[AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF +AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl +AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [ + ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS + _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1" + AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])], + [AS_VAR_SET(CACHEVAR,[yes])], + [AS_VAR_SET(CACHEVAR,[no])]) + _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags]) +AS_VAR_IF(CACHEVAR,yes, + [m4_default([$2], :)], + [m4_default([$3], :)]) +AS_VAR_POPDEF([CACHEVAR])dnl +])dnl AX_CHECK_COMPILE_FLAGS diff --git a/elpa/pdf-tools-20211110.513/build/server/poppler-hack.cc b/elpa/pdf-tools-20211110.513/build/server/poppler-hack.cc @@ -0,0 +1,122 @@ +// Copyright (C) 2013, 2014 Andreas Politz + +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include <config.h> +#include <PDFDocEncoding.h> +#include <Annot.h> +#include <glib.h> +#include <glib-object.h> +#include <poppler-features.h> + +extern "C" +{ + +GType poppler_annot_get_type (void) G_GNUC_CONST; +GType poppler_annot_markup_get_type (void) G_GNUC_CONST; + +#define POPPLER_TYPE_ANNOT (poppler_annot_get_type ()) +#define POPPLER_ANNOT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), POPPLER_TYPE_ANNOT, PopplerAnnot)) +#define POPPLER_IS_ANNOT_MARKUP(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), POPPLER_TYPE_ANNOT_MARKUP)) +#define POPPLER_TYPE_ANNOT_MARKUP (poppler_annot_markup_get_type ()) + +#if POPPLER_CHECK_VERSION(0,72,0) +#define GET_CSTR c_str +#else +#define GET_CSTR getCString +#endif + + struct PopplerAnnot + { + GObject parent_instance; + Annot *annot; + }; + + struct PopplerAnnotMarkup + { + GObject parent_instance; + }; + + struct PopplerRectangle + { + double x1; + double y1; + double x2; + double y2; + }; + + // This function does not modify its argument s, but for + // compatibility reasons (e.g. getLength in GooString.h before 2015) + // with older poppler code, it can't be declared as such. + char *_xpoppler_goo_string_to_utf8(/* const */ GooString *s) + { + char *result; + + if (! s) + return NULL; + + if (s->hasUnicodeMarker()) { + result = g_convert (s->GET_CSTR () + 2, + s->getLength () - 2, + "UTF-8", "UTF-16BE", NULL, NULL, NULL); + } else { + int len; + gunichar *ucs4_temp; + int i; + + len = s->getLength (); + ucs4_temp = g_new (gunichar, len + 1); + for (i = 0; i < len; ++i) { + ucs4_temp[i] = pdfDocEncoding[(unsigned char)s->getChar(i)]; + } + ucs4_temp[i] = 0; + + result = g_ucs4_to_utf8 (ucs4_temp, -1, NULL, NULL, NULL); + + g_free (ucs4_temp); + } + + return result; + } +#ifdef HAVE_POPPLER_ANNOT_WRITE + // Set the rectangle of an annotation. It was first added in v0.26. + void xpoppler_annot_set_rectangle (PopplerAnnot *a, PopplerRectangle *rectangle) + { + GooString *state = (GooString*) a->annot->getAppearState (); + char *ustate = _xpoppler_goo_string_to_utf8 (state); + + a->annot->setRect (rectangle->x1, rectangle->y1, + rectangle->x2, rectangle->y2); + a->annot->setAppearanceState (ustate); + g_free (ustate); + } +#endif + // This function is in the library, but the enforced date parsing is + // incomplete (at least in some versions), because it ignores the + // timezone. + gchar *xpoppler_annot_markup_get_created (PopplerAnnotMarkup *poppler_annot) + { + AnnotMarkup *annot; + GooString *text; + + g_return_val_if_fail (POPPLER_IS_ANNOT_MARKUP (poppler_annot), NULL); + + annot = static_cast<AnnotMarkup *>(POPPLER_ANNOT (poppler_annot)->annot); + text = (GooString*) annot->getDate (); + + return text ? _xpoppler_goo_string_to_utf8 (text) : NULL; + } +} diff --git a/elpa/pdf-tools-20211110.513/build/server/poppler-versions b/elpa/pdf-tools-20211110.513/build/server/poppler-versions @@ -0,0 +1,12 @@ +HAVE_POPPLER_ANNOT_WRITE +0.19.4 solves bug 49080, which potentially corrupts PDF files. + +HAVE_POPPLER_FIND_OPTS +0.22 PopplerFindFlags +0.22 poppler_page_find_text_with_options + +HAVE_POPPLER_ANNOT_SET_RECT +0.26 Adds function poppler_annot_set_rectangle + +HAVE_POPPLER_ANNOT_MARKUP +0.26 poppler_annot_text_markup_new_{highlight,squiggly,strikeout,underline} diff --git a/elpa/pdf-tools-20211110.513/build/server/synctex_parser.c b/elpa/pdf-tools-20211110.513/build/server/synctex_parser.c @@ -0,0 +1,8924 @@ +/* + Copyright (c) 2008-2017 jerome DOT laurens AT u-bourgogne DOT fr + + This file is part of the __SyncTeX__ package. + + [//]: # (Latest Revision: Sun Oct 15 15:09:55 UTC 2017) + [//]: # (Version: 1.21) + + See `synctex_parser_readme.md` for more details + + ## License + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE + + Except as contained in this notice, the name of the copyright holder + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization from the copyright holder. + + Acknowledgments: + ---------------- + The author received useful remarks from the pdfTeX developers, especially Hahn The Thanh, + and significant help from XeTeX developer Jonathan Kew + + Nota Bene: + ---------- + If you include or use a significant part of the synctex package into a software, + I would appreciate to be listed as contributor and see "SyncTeX" highlighted. + + */ + +/* We assume that high level application like pdf viewers will want + * to embed this code as is. We assume that they also have locale.h and setlocale. + * For other tools such as TeXLive tools, you must define SYNCTEX_USE_LOCAL_HEADER, + * when building. You also have to create and customize synctex_parser_local.h to fit your system. + * In particular, the HAVE_LOCALE_H and HAVE_SETLOCALE macros should be properly defined. + * With this design, you should not need to edit this file. */ + +/** + * \file synctex_parser.c + * \brief SyncTeX file parser and controller. + * - author: Jérôme LAURENS + * \version 1.21 + * \date Sun Oct 15 15:09:55 UTC 2017 + * + * Reads and parse *.synctex[.gz] files, + * performs edit and display queries. + * + * See + * - synctex_scanner_new_with_output_file + * - synctex_scanner_parse + * - synctex_scanner_free + * - synctex_display_query + * - synctex_edit_query + * - synctex_scanner_next_result + * - synctex_scanner_reset_result + * + * The data is organized in a graph with multiple entries. + * The root object is a scanner, it is created with the contents on a synctex file. + * Each node of the tree is a synctex_node_t object. + * There are 3 subtrees, two of them sharing the same leaves. + * The first tree is the list of input records, where input file names are associated with tags. + * The second tree is the box tree as given by TeX when shipping pages out. + * First level objects are sheets and forms, containing boxes, glues, kerns... + * The third tree allows to browse leaves according to tag and line. + */ +# if defined(SYNCTEX_USE_LOCAL_HEADER) +# include "synctex_parser_local.h" +# else +# define HAVE_LOCALE_H 1 +# define HAVE_SETLOCALE 1 +# if defined(_MSC_VER) +# define SYNCTEX_INLINE __inline +# else +# define SYNCTEX_INLINE inline +# endif +# endif + +#include <stdlib.h> +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <limits.h> + +#if defined(HAVE_LOCALE_H) +#include <locale.h> +#endif + +/* Mark unused parameters, so that there will be no compile warnings. */ +#ifdef __DARWIN_UNIX03 +# define SYNCTEX_UNUSED(x) SYNCTEX_PRAGMA(unused(x)) +# define SYNCTEX_PRAGMA(x) _Pragma ( #x ) +#else +# define SYNCTEX_UNUSED(x) (void)(x); +#endif + +#include "synctex_parser_advanced.h" + +SYNCTEX_INLINE static int _synctex_abs(int x) { + return x>0? x: -x; +} +/* These are the possible extensions of the synctex file */ +const char * synctex_suffix = ".synctex"; +const char * synctex_suffix_gz = ".gz"; + +typedef synctex_node_p(*synctex_node_new_f)(synctex_scanner_p); +typedef void(*synctex_node_fld_f)(synctex_node_p); +typedef char *(*synctex_node_str_f)(synctex_node_p); + +/** + * Pseudo class. + * - author: J. Laurens + * + * Each nodes has a class, it is therefore called an object. + * Each class has a unique scanner. + * Each class has a type which is a unique identifier. + * The class points to various methods, + * each of them vary amongst objects. + * Each class has a data model which stores node's attributes. + * Each class has an tree model which stores children and parent. + * Inspectors give access to data and tree elements. + */ + +/* 8 fields + size: spcflnat */ +typedef struct synctex_tree_model_t { + int sibling; + int parent; + int child; + int friend; + int last; + int next_hbox; + int arg_sibling; + int target; + int size; +} synctex_tree_model_s; +typedef const synctex_tree_model_s * synctex_tree_model_p; + +typedef struct synctex_data_model_t { + int tag; + int line; + int column; + int h; + int v; + int width; + int height; + int depth; + int mean_line; + int weight; + int h_V; + int v_V; + int width_V; + int height_V; + int depth_V; + int name; + int page; + int size; +} synctex_data_model_s; + +typedef const synctex_data_model_s * synctex_data_model_p; + +typedef int (*synctex_int_getter_f)(synctex_node_p); +typedef struct synctex_tlcpector_t { + synctex_int_getter_f tag; + synctex_int_getter_f line; + synctex_int_getter_f column; +} synctex_tlcpector_s; +typedef const synctex_tlcpector_s * synctex_tlcpector_p; +static int _synctex_int_none(synctex_node_p node) { + SYNCTEX_UNUSED(node) + return 0; +} +static const synctex_tlcpector_s synctex_tlcpector_none = { + &_synctex_int_none, /* tag */ + &_synctex_int_none, /* line */ + &_synctex_int_none, /* column */ +}; + +typedef struct synctex_inspector_t { + synctex_int_getter_f h; + synctex_int_getter_f v; + synctex_int_getter_f width; + synctex_int_getter_f height; + synctex_int_getter_f depth; +} synctex_inspector_s; +typedef const synctex_inspector_s * synctex_inspector_p; +static const synctex_inspector_s synctex_inspector_none = { + &_synctex_int_none, /* h */ + &_synctex_int_none, /* v */ + &_synctex_int_none, /* width */ + &_synctex_int_none, /* height */ + &_synctex_int_none, /* depth */ +}; + +typedef float (*synctex_float_getter_f)(synctex_node_p); +typedef struct synctex_vispector_t { + synctex_float_getter_f h; + synctex_float_getter_f v; + synctex_float_getter_f width; + synctex_float_getter_f height; + synctex_float_getter_f depth; +} synctex_vispector_s; +static float _synctex_float_none(synctex_node_p node) { + SYNCTEX_UNUSED(node) + return 0; +} +static const synctex_vispector_s synctex_vispector_none = { + &_synctex_float_none, /* h */ + &_synctex_float_none, /* v */ + &_synctex_float_none, /* width */ + &_synctex_float_none, /* height */ + &_synctex_float_none, /* depth */ +}; +typedef const synctex_vispector_s * synctex_vispector_p; + +struct synctex_class_t { + synctex_scanner_p scanner; + synctex_node_type_t type; + synctex_node_new_f new; + synctex_node_fld_f free; + synctex_node_fld_f log; + synctex_node_fld_f display; + synctex_node_str_f abstract; + synctex_tree_model_p navigator; + synctex_data_model_p modelator; + synctex_tlcpector_p tlcpector; + synctex_inspector_p inspector; + synctex_vispector_p vispector; +}; + +/** + * Nota bene: naming convention. + * For static API, when the name contains "proxy", it applies to proxies. + * When the name contains "noxy", it applies to non proxies only. + * When the name contains "node", well it depends... + */ + +typedef synctex_node_p synctex_proxy_p; +typedef synctex_node_p synctex_noxy_p; + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Abstract OBJECTS and METHODS +# endif + +/** + * \def SYNCTEX_MSG_SEND + * \brief Takes care of sending the given message if possible. + * - parameter NODE: of type synctex_node_p + * - parameter SELECTOR: one of the class_ pointer properties + */ +# define SYNCTEX_MSG_SEND(NODE,SELECTOR) do {\ + synctex_node_p N__ = NODE;\ + if (N__ && N__->class_->SELECTOR) {\ + (*(N__->class_->SELECTOR))(N__);\ + }\ +} while (synctex_NO) + +/** + * Free the given node by sending the free message. + * - parameter NODE: of type synctex_node_p + */ +void synctex_node_free(synctex_node_p node) { + SYNCTEX_MSG_SEND(node,free); +} +# if defined(SYNCTEX_TESTING) +# if !defined(SYNCTEX_USE_HANDLE) +# define SYNCTEX_USE_HANDLE 1 +# endif +# if !defined(SYNCTEX_USE_CHARINDEX) +# define SYNCTEX_USE_CHARINDEX 1 +# endif +# endif +SYNCTEX_INLINE static synctex_node_p _synctex_new_handle_with_target(synctex_node_p target); +# if defined(SYNCTEX_USE_HANDLE) +# define SYNCTEX_SCANNER_FREE_HANDLE(SCANR) \ +__synctex_scanner_free_handle(SCANR) +# define SYNCTEX_SCANNER_REMOVE_HANDLE_TO(WHAT) \ +__synctex_scanner_remove_handle_to(WHAT) +# define SYNCTEX_REGISTER_HANDLE_TO(NODE) \ +__synctex_scanner_register_handle_to(NODE) +# else +# define SYNCTEX_SCANNER_FREE_HANDLE(SCANR) +# define SYNCTEX_SCANNER_REMOVE_HANDLE_TO(WHAT) +# define SYNCTEX_REGISTER_HANDLE_TO(NODE) +# endif + +# if defined(SYNCTEX_USE_CHARINDEX) +# define SYNCTEX_CHARINDEX(NODE) (NODE->char_index) +# define SYNCTEX_LINEINDEX(NODE) (NODE->line_index) +# define SYNCTEX_PRINT_CHARINDEX_FMT "#%i" +# define SYNCTEX_PRINT_CHARINDEX_WHAT ,SYNCTEX_CHARINDEX(node) +# define SYNCTEX_PRINT_CHARINDEX \ + printf(SYNCTEX_PRINT_CHARINDEX_FMT SYNCTEX_PRINT_CHARINDEX_WHAT) +# define SYNCTEX_PRINT_LINEINDEX_FMT "L#%i" +# define SYNCTEX_PRINT_LINEINDEX_WHAT ,SYNCTEX_LINEINDEX(node) +# define SYNCTEX_PRINT_LINEINDEX \ + printf(SYNCTEX_PRINT_LINEINDEX_FMT SYNCTEX_PRINT_LINEINDEX_WHAT) +# define SYNCTEX_PRINT_CHARINDEX_NL \ + printf(SYNCTEX_PRINT_CHARINDEX_FMT "\n" SYNCTEX_PRINT_CHARINDEX_WHAT) +# define SYNCTEX_PRINT_LINEINDEX_NL \ + printf(SYNCTEX_PRINT_CHARINDEX_FMT "\n"SYNCTEX_PRINT_LINEINDEX_WHAT) +# define SYNCTEX_IMPLEMENT_CHARINDEX(NODE,CORRECTION)\ + NODE->char_index = (synctex_charindex_t)(scanner->reader->charindex_offset+SYNCTEX_CUR-SYNCTEX_START+(CORRECTION)); \ + NODE->line_index = scanner->reader->line_number; +# else +# define SYNCTEX_CHARINDEX(NODE) 0 +# define SYNCTEX_LINEINDEX(NODE) 0 +# define SYNCTEX_PRINT_CHARINDEX_FMT +# define SYNCTEX_PRINT_CHARINDEX_WHAT +# define SYNCTEX_PRINT_CHARINDEX +# define SYNCTEX_PRINT_CHARINDEX +# define SYNCTEX_PRINT_LINEINDEX_FMT +# define SYNCTEX_PRINT_LINEINDEX_WHAT +# define SYNCTEX_PRINT_LINEINDEX +# define SYNCTEX_PRINT_CHARINDEX_NL printf("\n") +# define SYNCTEX_PRINT_LINEINDEX_NL printf("\n") +# define SYNCTEX_IMPLEMENT_CHARINDEX(NODE,CORRECTION) +# endif + +/** + * The next macros are used to access the node tree info + * SYNCTEX_DATA(node) points to the first synctex integer or pointer data of node + * SYNCTEX_DATA(node)[index] is the information at index + * for example, the page of a sheet is stored in SYNCTEX_DATA(sheet)[_synctex_data_page_idx] + * - parameter NODE: of type synctex_node_p + * If the name starts with "__", the argument is nonullable + */ +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Tree SETGET +# endif + +#if SYNCTEX_DEBUG > 1000 +#define SYNCTEX_PARAMETER_ASSERT(WHAT) \ + do { \ + if (!(WHAT)) { \ + printf("! Parameter failure: %s\n",#WHAT); \ + } \ + } while (synctex_NO) +#define DEFINE_SYNCTEX_TREE_HAS(WHAT)\ +static synctex_bool_t _synctex_tree_has_##WHAT(synctex_node_p node) {\ + if (node) {\ + if (node->class_->navigator->WHAT>=0) {\ + return synctex_YES; \ + } else {\ + printf("WARNING: NO tree %s for %s\n", #WHAT, synctex_node_isa(node));\ + }\ + }\ + return synctex_NO;\ +} +#else +#define SYNCTEX_PARAMETER_ASSERT(WHAT) +#define DEFINE_SYNCTEX_TREE_HAS(WHAT) \ +static synctex_bool_t _synctex_tree_has_##WHAT(synctex_node_p node) {\ + return (node && (node->class_->navigator->WHAT>=0));\ +} +#endif + +# define DEFINE_SYNCTEX_TREE__GET(WHAT) \ +SYNCTEX_INLINE static synctex_node_p __synctex_tree_##WHAT(synctex_non_null_node_p node) {\ + return node->data[node->class_->navigator->WHAT].as_node;\ +} +# define DEFINE_SYNCTEX_TREE_GET(WHAT) \ +DEFINE_SYNCTEX_TREE__GET(WHAT) \ +static synctex_node_p _synctex_tree_##WHAT(synctex_node_p node) {\ + if (_synctex_tree_has_##WHAT(node)) {\ + return __synctex_tree_##WHAT(node);\ + }\ + return 0;\ +} +# define DEFINE_SYNCTEX_TREE__RESET(WHAT) \ +SYNCTEX_INLINE static synctex_node_p __synctex_tree_reset_##WHAT(synctex_non_null_node_p node) {\ + synctex_node_p old = node->data[node->class_->navigator->WHAT].as_node;\ + node->data[node->class_->navigator->WHAT].as_node=NULL;\ + return old;\ +} +# define DEFINE_SYNCTEX_TREE_RESET(WHAT) \ +DEFINE_SYNCTEX_TREE__RESET(WHAT) \ +SYNCTEX_INLINE static synctex_node_p _synctex_tree_reset_##WHAT(synctex_node_p node) {\ + return _synctex_tree_has_##WHAT(node)? \ + __synctex_tree_reset_##WHAT(node): NULL; \ +} +# define DEFINE_SYNCTEX_TREE__SET(WHAT) \ +SYNCTEX_INLINE static synctex_node_p __synctex_tree_set_##WHAT(synctex_non_null_node_p node, synctex_node_p new_value) {\ + synctex_node_p old = __synctex_tree_##WHAT(node);\ + node->data[node->class_->navigator->WHAT].as_node=new_value;\ + return old;\ +} +# define DEFINE_SYNCTEX_TREE_SET(WHAT) \ +DEFINE_SYNCTEX_TREE__SET(WHAT) \ +SYNCTEX_INLINE static synctex_node_p _synctex_tree_set_##WHAT(synctex_node_p node, synctex_node_p new_value) {\ + return _synctex_tree_has_##WHAT(node)?\ + __synctex_tree_set_##WHAT(node,new_value):NULL;\ +} +# define DEFINE_SYNCTEX_TREE__GETSETRESET(WHAT) \ +DEFINE_SYNCTEX_TREE__GET(WHAT) \ +DEFINE_SYNCTEX_TREE__SET(WHAT) \ +DEFINE_SYNCTEX_TREE__RESET(WHAT) + +# define DEFINE_SYNCTEX_TREE_GETSET(WHAT) \ +DEFINE_SYNCTEX_TREE_HAS(WHAT) \ +DEFINE_SYNCTEX_TREE_GET(WHAT) \ +DEFINE_SYNCTEX_TREE_SET(WHAT) + +# define DEFINE_SYNCTEX_TREE_GETRESET(WHAT) \ +DEFINE_SYNCTEX_TREE_HAS(WHAT) \ +DEFINE_SYNCTEX_TREE_GET(WHAT) \ +DEFINE_SYNCTEX_TREE_RESET(WHAT) + +# define DEFINE_SYNCTEX_TREE_GETSETRESET(WHAT) \ +DEFINE_SYNCTEX_TREE_HAS(WHAT) \ +DEFINE_SYNCTEX_TREE_GET(WHAT) \ +DEFINE_SYNCTEX_TREE_SET(WHAT) \ +DEFINE_SYNCTEX_TREE_RESET(WHAT) + +/* + * _synctex_tree_set_... methods return the old value. + * The return value of _synctex_tree_set_child and + * _synctex_tree_set_sibling must be released somehown. + */ +DEFINE_SYNCTEX_TREE__GETSETRESET(sibling) +DEFINE_SYNCTEX_TREE_GETSETRESET(parent) +DEFINE_SYNCTEX_TREE_GETSETRESET(child) +DEFINE_SYNCTEX_TREE_GETSETRESET(friend) +DEFINE_SYNCTEX_TREE_GETSET(last) +DEFINE_SYNCTEX_TREE_GETSET(next_hbox) +DEFINE_SYNCTEX_TREE_GETSET(arg_sibling) +DEFINE_SYNCTEX_TREE_GETSETRESET(target) + +#if SYNCTEX_DEBUG>1000 +# undef SYNCTEX_USE_NODE_COUNT +# define SYNCTEX_USE_NODE_COUNT 1 +#endif +#if SYNCTEX_USE_NODE_COUNT>0 +# define SYNCTEX_DECLARE_NODE_COUNT int node_count; +# define SYNCTEX_INIT_NODE_COUNT \ + do { node_count = 0; } while(synctex_NO) +#else +# define SYNCTEX_DECLARE_NODE_COUNT +# define SYNCTEX_INIT_NODE_COUNT +#endif + +#if SYNCTEX_USE_NODE_COUNT>10 +# define SYNCTEX_DID_NEW(N) _synctex_did_new(N) +# define SYNCTEX_WILL_FREE(N) _synctex_will_free(N) +#else +# define SYNCTEX_DID_NEW(N) +# define SYNCTEX_WILL_FREE(N) +#endif + +#define SYNCTEX_HAS_CHILDREN(NODE) (NODE && _synctex_tree_child(NODE)) +# ifdef __SYNCTEX_WORK__ +# include "/usr/include/zlib.h" +# else +# include <zlib.h> +# endif + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark STATUS +# endif +/* When the end of the synctex file has been reached: */ +# define SYNCTEX_STATUS_EOF 0 +/* When the function could not return the value it was asked for: */ +# define SYNCTEX_STATUS_NOT_OK (SYNCTEX_STATUS_EOF+1) +/* When the function returns the value it was asked for: + It must be the biggest one */ +# define SYNCTEX_STATUS_OK (SYNCTEX_STATUS_NOT_OK+1) +/* Generic error: */ +# define SYNCTEX_STATUS_ERROR (SYNCTEX_STATUS_EOF-1) +/* Parameter error: */ +# define SYNCTEX_STATUS_BAD_ARGUMENT (SYNCTEX_STATUS_ERROR-1) + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark File reader +# endif + +/* We ensure that SYNCTEX_BUFFER_SIZE < UINT_MAX, I don't know if it makes sense... */ +/* Actually, the minimum buffer size is driven by integer and float parsing, including the unit. + * ±0.123456789e123?? + */ +# define SYNCTEX_BUFFER_MIN_SIZE 32 +# define SYNCTEX_BUFFER_SIZE 32768 + +#if SYNCTEX_BUFFER_SIZE >= UINT_MAX +# error BAD BUFFER SIZE(1) +#endif +#if SYNCTEX_BUFFER_SIZE < SYNCTEX_BUFFER_MIN_SIZE +# error BAD BUFFER SIZE(2) +#endif + +typedef struct synctex_reader_t { + gzFile file; /* The (possibly compressed) file */ + char * output; + char * synctex; + char * current; /* current location in the buffer */ + char * start; /* start of the buffer */ + char * end; /* end of the buffer */ + size_t min_size; + size_t size; + int lastv; + int line_number; + SYNCTEX_DECLARE_CHAR_OFFSET +} synctex_reader_s; + +typedef synctex_reader_s * synctex_reader_p; + +typedef struct { + synctex_status_t status; + char * synctex; + gzFile file; + synctex_io_mode_t io_mode; +} synctex_open_s; + +/* This functions opens the file at the "output" given location. + * It manages the problem of quoted filenames that appear with pdftex and filenames containing the space character. + * In TeXLive 2008, the synctex file created with pdftex did contain unexpected quotes. + * This function will remove them if possible. + * All the reference arguments will take a value on return. They must be non NULL. + * - returns: an open structure which status is + * SYNCTEX_STATUS_OK on success, + * SYNCTEX_STATUS_ERROR on failure. + * - note: on success, the caller is the owner + * of the fields of the returned open structure. + */ +static synctex_open_s __synctex_open_v2(const char * output, synctex_io_mode_t io_mode, synctex_bool_t add_quotes) { + synctex_open_s open = {SYNCTEX_STATUS_ERROR, NULL, NULL, io_mode}; + char * quoteless_synctex_name = NULL; + const char * mode = _synctex_get_io_mode_name(open.io_mode); + size_t size = strlen(output)+strlen(synctex_suffix)+strlen(synctex_suffix_gz)+1; + if (NULL == (open.synctex = (char *)malloc(size))) { + _synctex_error("! __synctex_open_v2: Memory problem (1)\n"); + return open; + } + /* we have reserved for synctex enough memory to copy output (including its 2 eventual quotes), both suffices, + * including the terminating character. size is free now. */ + if (open.synctex != strcpy(open.synctex,output)) { + _synctex_error("! __synctex_open_v2: Copy problem\n"); + return_on_error: + free(open.synctex); + open.synctex = NULL; + free(quoteless_synctex_name);/* We MUST have quoteless_synctex_name<>synctex_name */ + return open; + } + /* remove the last path extension if any */ + _synctex_strip_last_path_extension(open.synctex); + if (!strlen(open.synctex)) { + goto return_on_error; + } + /* now insert quotes. */ + if (add_quotes) { + char * quoted = NULL; + if (_synctex_copy_with_quoting_last_path_component(open.synctex,&quoted,size) || quoted == NULL) { + /* There was an error or quoting does not make sense: */ + goto return_on_error; + } + quoteless_synctex_name = open.synctex; + open.synctex = quoted; + } + /* Now add to open.synctex the first path extension. */ + if (open.synctex != strcat(open.synctex,synctex_suffix)){ + _synctex_error("! __synctex_open_v2: Concatenation problem (can't add suffix '%s')\n",synctex_suffix); + goto return_on_error; + } + /* Add to quoteless_synctex_name as well, if relevant. */ + if (quoteless_synctex_name && (quoteless_synctex_name != strcat(quoteless_synctex_name,synctex_suffix))){ + free(quoteless_synctex_name); + quoteless_synctex_name = NULL; + } + if (NULL == (open.file = gzopen(open.synctex,mode))) { + /* Could not open this file */ + if (errno != ENOENT) { + /* The file does exist, this is a lower level error, I can't do anything. */ + _synctex_error("could not open %s, error %i\n",open.synctex,errno); + goto return_on_error; + } + /* Apparently, there is no uncompressed synctex file. Try the compressed version */ + if (open.synctex != strcat(open.synctex,synctex_suffix_gz)){ + _synctex_error("! __synctex_open_v2: Concatenation problem (can't add suffix '%s')\n",synctex_suffix_gz); + goto return_on_error; + } + open.io_mode |= synctex_io_gz_mask; + mode = _synctex_get_io_mode_name(open.io_mode); /* the file is a compressed and is a binary file, this caused errors on Windows */ + /* Add the suffix to the quoteless_synctex_name as well. */ + if (quoteless_synctex_name && (quoteless_synctex_name != strcat(quoteless_synctex_name,synctex_suffix_gz))){ + free(quoteless_synctex_name); + quoteless_synctex_name = NULL; + } + if (NULL == (open.file = gzopen(open.synctex,mode))) { + /* Could not open this file */ + if (errno != ENOENT) { + /* The file does exist, this is a lower level error, I can't do anything. */ + _synctex_error("Could not open %s, error %i\n",open.synctex,errno); + } + goto return_on_error; + } + } + /* At this point, the file is properly open. + * If we are in the add_quotes mode, we change the file name by removing the quotes. */ + if (quoteless_synctex_name) { + gzclose(open.file); + if (rename(open.synctex,quoteless_synctex_name)) { + _synctex_error("Could not rename %s to %s, error %i\n",open.synctex,quoteless_synctex_name,errno); + /* We could not rename, reopen the file with the quoted name. */ + if (NULL == (open.file = gzopen(open.synctex,mode))) { + /* No luck, could not re open this file, something has happened meanwhile */ + if (errno != ENOENT) { + /* The file does not exist any more, it has certainly be removed somehow + * this is a lower level error, I can't do anything. */ + _synctex_error("Could not open again %s, error %i\n",open.synctex,errno); + } + goto return_on_error; + } + } else { + /* The file has been successfully renamed */ + if (NULL == (open.file = gzopen(quoteless_synctex_name,mode))) { + /* Could not open this file */ + if (errno != ENOENT) { + /* The file does exist, this is a lower level error, I can't do anything. */ + _synctex_error("Could not open renamed %s, error %i\n",quoteless_synctex_name,errno); + } + goto return_on_error; + } + /* The quote free file name should replace the old one:*/ + free(open.synctex); + open.synctex = quoteless_synctex_name; + quoteless_synctex_name = NULL; + } + } + /* The operation is successful, return the arguments by value. */ + open.status = SYNCTEX_STATUS_OK; + return open; +} + +/* Opens the output file, taking into account the eventual build_directory. + * - returns: an open structure which status is + * SYNCTEX_STATUS_OK on success, + * SYNCTEX_STATUS_ERROR on failure. + * - note: on success, the caller is the owner + * of the fields of the returned open structure. + */ +static synctex_open_s _synctex_open_v2(const char * output, const char * build_directory, synctex_io_mode_t io_mode, synctex_bool_t add_quotes) { + synctex_open_s open = __synctex_open_v2(output,io_mode,add_quotes); + if (open.status == SYNCTEX_STATUS_OK) { + return open; + } + if (build_directory && strlen(build_directory)) { + char * build_output; + const char *lpc; + size_t size; + synctex_bool_t is_absolute; + build_output = NULL; + lpc = _synctex_last_path_component(output); + size = strlen(build_directory)+strlen(lpc)+2; /* One for the '/' and one for the '\0'. */ + is_absolute = _synctex_path_is_absolute(build_directory); + if (!is_absolute) { + size += strlen(output); + } + if ((build_output = (char *)_synctex_malloc(size))) { + if (is_absolute) { + build_output[0] = '\0'; + } else { + if (build_output != strcpy(build_output,output)) { + _synctex_free(build_output); + return open; + } + build_output[lpc-output]='\0'; + } + if (build_output == strcat(build_output,build_directory)) { + /* Append a path separator if necessary. */ + if (!SYNCTEX_IS_PATH_SEPARATOR(build_output[strlen(build_directory)-1])) { + if (build_output != strcat(build_output,"/")) { + _synctex_free(build_output); + return open; + } + } + /* Append the last path component of the output. */ + if (build_output != strcat(build_output,lpc)) { + _synctex_free(build_output); + return open; + } + open = __synctex_open_v2(build_output,io_mode,add_quotes); + } + _synctex_free(build_output); + } /* if ((build_output... */ + } /* if (build_directory...) */ + return open; +} +void synctex_reader_free(synctex_reader_p reader) { + if (reader) { + _synctex_free(reader->output); + _synctex_free(reader->synctex); + _synctex_free(reader->start); + gzclose(reader->file); + _synctex_free(reader); + } +} +/* + * Return reader on success. + * Deallocate reader and return NULL on failure. + */ +synctex_reader_p synctex_reader_init_with_output_file(synctex_reader_p reader, const char * output, const char * build_directory) { + if (reader) { + /* now open the synctex file */ + synctex_open_s open = _synctex_open_v2(output,build_directory,0,synctex_ADD_QUOTES); + if (open.status<SYNCTEX_STATUS_OK) { + open = _synctex_open_v2(output,build_directory,0,synctex_DONT_ADD_QUOTES); + if (open.status<SYNCTEX_STATUS_OK) { + return NULL; + } + } + reader->synctex = open.synctex; + reader->file = open.file; + /* make a private copy of output */ + if (NULL == (reader->output = (char *)_synctex_malloc(strlen(output)+1))){ + _synctex_error("! synctex_scanner_new_with_output_file: Memory problem (2), reader's output is not reliable."); + } else if (reader->output != strcpy(reader->output,output)) { + _synctex_free(reader->output); + reader->output = NULL; + _synctex_error("! synctex_scanner_new_with_output_file: Copy problem, reader's output is not reliable."); + } + reader->start = reader->end = reader->current = NULL; + reader->min_size = SYNCTEX_BUFFER_MIN_SIZE; + reader->size = SYNCTEX_BUFFER_SIZE; + reader->start = reader->current = + (char *)_synctex_malloc(reader->size+1); /* one more character for null termination */ + if (NULL == reader->start) { + _synctex_error("! malloc error in synctex_reader_init_with_output_file."); + bailey: +#ifdef SYNCTEX_DEBUG + return reader; +#else + synctex_reader_free(reader); + return NULL; +#endif + } + reader->end = reader->start+reader->size; + /* reader->end always points to a null terminating character. + * Maybe there is another null terminating character between reader->current and reader->end-1. + * At least, we are sure that reader->current points to a string covering a valid part of the memory. */ +# if defined(SYNCTEX_USE_CHARINDEX) + reader->charindex_offset = -reader->size; +# endif + } + return reader; +} + +# if defined(SYNCTEX_USE_HANDLE) +# define SYNCTEX_DECLARE_HANDLE synctex_node_p handle; +# else +# define SYNCTEX_DECLARE_HANDLE +# endif + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark SCANNER +# endif +/** + * The synctex scanner is the root object. + * Is is initialized with the contents of a text file or a gzipped file. + * The buffer_.* are first used to parse the text. + */ +struct synctex_scanner_t { + synctex_reader_p reader; + SYNCTEX_DECLARE_NODE_COUNT + SYNCTEX_DECLARE_HANDLE + char * output_fmt; /* dvi or pdf, not yet used */ + synctex_iterator_p iterator;/* result iterator */ + int version; /* 1, not yet used */ + struct { + unsigned has_parsed:1; /* Whether the scanner has parsed its underlying synctex file. */ + unsigned postamble:1; /* Whether the scanner has parsed its underlying synctex file. */ + unsigned reserved:sizeof(unsigned)-2; /* alignment */ + } flags; + int pre_magnification; /* magnification from the synctex preamble */ + int pre_unit; /* unit from the synctex preamble */ + int pre_x_offset; /* X offset from the synctex preamble */ + int pre_y_offset; /* Y offset from the synctex preamble */ + int count; /* Number of records, from the synctex postamble */ + float unit; /* real unit, from synctex preamble or post scriptum */ + float x_offset; /* X offset, from synctex preamble or post scriptum */ + float y_offset; /* Y Offset, from synctex preamble or post scriptum */ + synctex_node_p input; /* The first input node, its siblings are the other input nodes */ + synctex_node_p sheet; /* The first sheet node, its siblings are the other sheet nodes */ + synctex_node_p form; /* The first form, its siblings are the other forms */ + synctex_node_p ref_in_sheet; /* The first form ref node in sheet, its friends are the other form ref nodes */ + synctex_node_p ref_in_form; /* The first form ref node, its friends are the other form ref nodes in sheet */ + int number_of_lists; /* The number of friend lists */ + synctex_node_r lists_of_friends;/* The friend lists */ + synctex_class_s class_[synctex_node_number_of_types]; /* The classes of the nodes of the scanner */ + int display_switcher; + char * display_prompt; +}; + +/** + * Create a new node of the given type. + * - parameter scanner: of type synctex_node_p + * - parameter type: a type, the client is responsible + * to ask for an acceptable type. + */ +synctex_node_p synctex_node_new(synctex_scanner_p scanner, synctex_node_type_t type) { + return scanner? scanner->class_[type].new(scanner):NULL; +} +# if defined(SYNCTEX_USE_HANDLE) +SYNCTEX_INLINE static void __synctex_scanner_free_handle(synctex_scanner_p scanner) { + synctex_node_free(scanner->handle); +} +SYNCTEX_INLINE static void __synctex_scanner_remove_handle_to(synctex_node_p node) { + synctex_node_p arg_sibling = NULL; + synctex_node_p handle = node->class_->scanner->handle; + while (handle) { + synctex_node_p sibling; + if (node == _synctex_tree_target(handle)) { + sibling = __synctex_tree_reset_sibling(handle); + if (arg_sibling) { + __synctex_tree_set_sibling(arg_sibling, sibling); + } else { + node->class_->scanner->handle = sibling; + } + synctex_node_free(handle); + break; + } else { + sibling = __synctex_tree_sibling(handle); + } + arg_sibling = handle; + handle = sibling; + } +} +SYNCTEX_INLINE static void __synctex_scanner_register_handle_to(synctex_node_p node) { + synctex_node_p NNN = _synctex_new_handle_with_target(node); + __synctex_tree_set_sibling(NNN,node->class_->scanner->handle); + node->class_->scanner->handle = NNN; +} +#endif +#if SYNCTEX_USE_NODE_COUNT>10 +SYNCTEX_INLINE static void _synctex_did_new(synctex_node_p node) { + printf("NODE CREATED # %i, %s, %p\n", + (node->class_->scanner->node_count)++, + synctex_node_isa(node), + node); +} +SYNCTEX_INLINE static void _synctex_will_free(synctex_node_p node) { + printf("NODE DELETED # %i, %s, %p\n", + --(node->class_->scanner->node_count), + synctex_node_isa(node), + node); +} +#endif + +/** + * Free the given node. + * - parameter node: of type synctex_node_p + * - note: a node is meant to own its child and sibling. + * It is not owned by its parent, unless it is its first child. + * This destructor is for all nodes with children. + */ +static void _synctex_free_node(synctex_node_p node) { + if (node) { + SYNCTEX_SCANNER_REMOVE_HANDLE_TO(node); + SYNCTEX_WILL_FREE(node); + synctex_node_free(__synctex_tree_sibling(node)); + synctex_node_free(_synctex_tree_child(node)); + _synctex_free(node); + } + return; +} +/** + * Free the given handle. + * - parameter node: of type synctex_node_p + * - note: a node is meant to own its child and sibling. + * It is not owned by its parent, unless it is its first child. + * This destructor is for all handles. + */ +static void _synctex_free_handle(synctex_node_p handle) { + if (handle) { + _synctex_free_handle(__synctex_tree_sibling(handle)); + _synctex_free_handle(_synctex_tree_child(handle)); + _synctex_free(handle); + } + return; +} + +/** + * Free the given leaf node. + * - parameter node: of type synctex_node_p, with no child nor sibling. + * - note: a node is meant to own its child and sibling. + * It is not owned by its parent, unless it is its first child. + * This destructor is for all nodes with no children. + */ +static void _synctex_free_leaf(synctex_node_p node) { + if (node) { + SYNCTEX_SCANNER_REMOVE_HANDLE_TO(node); + SYNCTEX_WILL_FREE(node); + synctex_node_free(__synctex_tree_sibling(node)); + _synctex_free(node); + } + return; +} + +/** + SYNCTEX_CUR, SYNCTEX_START and SYNCTEX_END are convenient shortcuts + */ +# define SYNCTEX_CUR (scanner->reader->current) +# define SYNCTEX_START (scanner->reader->start) +# define SYNCTEX_END (scanner->reader->end) + +/* Here are gathered all the possible status that the next scanning functions will return. + * All these functions return a status, and pass their result through pointers. + * Negative values correspond to errors. + * The management of the buffer is causing some significant overhead. + * Every function that may access the buffer returns a status related to the buffer and file state. + * status >= SYNCTEX_STATUS_OK means the function worked as expected + * status < SYNCTEX_STATUS_OK means the function did not work as expected + * status == SYNCTEX_STATUS_NOT_OK means the function did not work as expected but there is still some material to parse. + * status == SYNCTEX_STATUS_EOF means the function did not work as expected and there is no more material. + * status<SYNCTEX_STATUS_EOF means an error + */ +#if defined(SYNCTEX_USE_CHARINDEX) +synctex_node_p synctex_scanner_handle(synctex_scanner_p scanner) { + return scanner? scanner->handle:NULL; +} +#endif + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Decoding prototypes +# endif + +typedef struct { + int integer; + synctex_status_t status; +} synctex_is_s; + +static synctex_is_s _synctex_decode_int(synctex_scanner_p scanner); +static synctex_is_s _synctex_decode_int_opt(synctex_scanner_p scanner, int default_value); +static synctex_is_s _synctex_decode_int_v(synctex_scanner_p scanner); + +typedef struct { + char * string; + synctex_status_t status; +} synctex_ss_s; + +static synctex_ss_s _synctex_decode_string(synctex_scanner_p scanner); + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Data SETGET +# endif + +/** + * The next macros are used to access the node data info + * through the class modelator integer fields. + * - parameter NODE: of type synctex_node_p + */ +# define SYNCTEX_DATA(NODE) ((*((((NODE)->class_))->info))(NODE)) +#if defined SYNCTEX_DEBUG > 1000 +# define DEFINE_SYNCTEX_DATA_HAS(WHAT) \ +SYNCTEX_INLINE static synctex_bool_t __synctex_data_has_##WHAT(synctex_node_p node) {\ + return (node && (node->class_->modelator->WHAT>=0));\ +}\ +SYNCTEX_INLINE static synctex_bool_t _synctex_data_has_##WHAT(synctex_node_p node) {\ + if (node && (node->class_->modelator->WHAT<0)) {\ + printf("WARNING: NO %s for %s\n", #WHAT, synctex_node_isa(node));\ + }\ + return __synctex_data_has_##WHAT(node);\ +} +#else +# define DEFINE_SYNCTEX_DATA_HAS(WHAT) \ +SYNCTEX_INLINE static synctex_bool_t __synctex_data_has_##WHAT(synctex_node_p node) {\ + return (node && (node->class_->modelator->WHAT>=0));\ +}\ +SYNCTEX_INLINE static synctex_bool_t _synctex_data_has_##WHAT(synctex_node_p node) {\ + return __synctex_data_has_##WHAT(node);\ +} +#endif + +SYNCTEX_INLINE static synctex_data_p __synctex_data(synctex_node_p node) { + return node->data+node->class_->navigator->size; +} +# define DEFINE_SYNCTEX_DATA_INT_GETSET(WHAT) \ +DEFINE_SYNCTEX_DATA_HAS(WHAT)\ +static int _synctex_data_##WHAT(synctex_node_p node) {\ + if (_synctex_data_has_##WHAT(node)) {\ + return __synctex_data(node)[node->class_->modelator->WHAT].as_integer;\ + }\ + return 0;\ +}\ +static int _synctex_data_set_##WHAT(synctex_node_p node, int new_value) {\ + int old = 0;\ + if (_synctex_data_has_##WHAT(node)) {\ + old = __synctex_data(node)[node->class_->modelator->WHAT].as_integer;\ + __synctex_data(node)[node->class_->modelator->WHAT].as_integer=new_value;\ + }\ + return old;\ +} +#define DEFINE_SYNCTEX_DATA_INT_DECODE(WHAT) \ +static synctex_status_t _synctex_data_decode_##WHAT(synctex_node_p node) {\ + if (_synctex_data_has_##WHAT(node)) {\ + synctex_is_s is = _synctex_decode_int(node->class_->scanner);\ + if (is.status == SYNCTEX_STATUS_OK) {\ + _synctex_data_set_##WHAT(node,is.integer);\ + } \ + return is.status;\ + }\ + return SYNCTEX_STATUS_BAD_ARGUMENT;\ +} +# define DEFINE_SYNCTEX_DATA_INT_DECODE_v(WHAT) \ +static synctex_status_t _synctex_data_decode_##WHAT##_v(synctex_node_p node) {\ + if (_synctex_data_has_##WHAT(node)) {\ + synctex_is_s is = _synctex_decode_int_v(node->class_->scanner);\ + if (is.status == SYNCTEX_STATUS_OK) {\ + _synctex_data_set_##WHAT(node,is.integer);\ + } \ + return is.status;\ + }\ + return SYNCTEX_STATUS_BAD_ARGUMENT;\ +} +#define DEFINE_SYNCTEX_DATA_STR_GETSET(WHAT) \ +DEFINE_SYNCTEX_DATA_HAS(WHAT)\ +static char * _synctex_data_##WHAT(synctex_node_p node) {\ + if (_synctex_data_has_##WHAT(node)) {\ + return node->data[node->class_->navigator->size+node->class_->modelator->WHAT].as_string;\ + }\ + return NULL;\ +}\ +static char * _synctex_data_set_##WHAT(synctex_node_p node, char * new_value) {\ + char * old = "";\ + if (_synctex_data_has_##WHAT(node)) {\ + old = node->data[node->class_->navigator->size+node->class_->modelator->WHAT].as_string;\ + node->data[node->class_->navigator->size+node->class_->modelator->WHAT].as_string =new_value;\ + }\ + return old;\ +} +#define DEFINE_SYNCTEX_DATA_STR_DECODE(WHAT) \ +static synctex_status_t _synctex_data_decode_##WHAT(synctex_node_p node) {\ + if (_synctex_data_has_##WHAT(node)) {\ + synctex_ss_s ss = _synctex_decode_string(node->class_->scanner);\ + if (ss.status == SYNCTEX_STATUS_OK) {\ + _synctex_data_set_##WHAT(node,ss.string);\ + } \ + return ss.status;\ + }\ + return SYNCTEX_STATUS_BAD_ARGUMENT;\ +} +#define DEFINE_SYNCTEX_DATA_INT_GETSET_DECODE(WHAT) \ +DEFINE_SYNCTEX_DATA_INT_GETSET(WHAT) \ +DEFINE_SYNCTEX_DATA_INT_DECODE(WHAT) +#define DEFINE_SYNCTEX_DATA_INT_GETSET_DECODE_v(WHAT) \ +DEFINE_SYNCTEX_DATA_INT_GETSET(WHAT) \ +DEFINE_SYNCTEX_DATA_INT_DECODE_v(WHAT) +#define DEFINE_SYNCTEX_DATA_STR_GETSET_DECODE(WHAT) \ +DEFINE_SYNCTEX_DATA_STR_GETSET(WHAT) \ +DEFINE_SYNCTEX_DATA_STR_DECODE(WHAT) + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark OBJECTS, their creators and destructors. +# endif + +# ifdef SYNCTEX_NOTHING +# pragma mark input. +# endif + +DEFINE_SYNCTEX_DATA_INT_GETSET_DECODE(tag) +DEFINE_SYNCTEX_DATA_INT_GETSET_DECODE(line) +DEFINE_SYNCTEX_DATA_STR_GETSET_DECODE(name) + +/* Input nodes only know about their sibling, which is another input node. + * The synctex information is the _synctex_data_tag and _synctex_data_name + * note: the input owns its name. */ + +# define SYNCTEX_INPUT_MARK "Input:" + +static const synctex_tree_model_s synctex_tree_model_input = { + synctex_tree_sibling_idx, /* sibling */ + -1, /* parent */ + -1, /* child */ + -1, /* friend */ + -1, /* last */ + -1, /* next_hbox */ + -1, /* arg_sibling */ + -1, /* target */ + synctex_tree_s_input_max +}; +static const synctex_data_model_s synctex_data_model_input = { + synctex_data_input_tag_idx, /* tag */ + synctex_data_input_line_idx,/* line */ + -1, /* column */ + -1, /* h */ + -1, /* v */ + -1, /* width */ + -1, /* height */ + -1, /* depth */ + -1, /* mean_line */ + -1, /* weight */ + -1, /* h_V */ + -1, /* v_V */ + -1, /* width_V */ + -1, /* height_V */ + -1, /* depth_V */ + synctex_data_input_name_idx, /* name */ + -1, /* page */ + synctex_data_input_tln_max +}; + +#define SYNCTEX_INSPECTOR_GETTER_F(WHAT)\ +&_synctex_data_##WHAT, &_synctex_data_set_##WHAT + +static synctex_node_p _synctex_new_input(synctex_scanner_p scanner); +static void _synctex_free_input(synctex_node_p node); +static void _synctex_log_input(synctex_node_p node); +static char * _synctex_abstract_input(synctex_node_p node); +static void _synctex_display_input(synctex_node_p node); + +static const synctex_tlcpector_s synctex_tlcpector_input = { + &_synctex_data_tag, /* tag */ + &_synctex_int_none, /* line */ + &_synctex_int_none, /* column */ +}; + +static synctex_class_s synctex_class_input = { + NULL, /* No scanner yet */ + synctex_node_type_input, /* Node type */ + &_synctex_new_input, /* creator */ + &_synctex_free_input, /* destructor */ + &_synctex_log_input, /* log */ + &_synctex_display_input, /* display */ + &_synctex_abstract_input, /* abstract */ + &synctex_tree_model_input, /* tree model */ + &synctex_data_model_input, /* data model */ + &synctex_tlcpector_input, /* inspector */ + &synctex_inspector_none, /* inspector */ + &synctex_vispector_none, /* vispector */ +}; + +typedef struct { + SYNCTEX_DECLARE_CHARINDEX + synctex_class_p class_; + synctex_data_u data[synctex_tree_s_input_max+synctex_data_input_tln_max]; +} synctex_input_s; + +static synctex_node_p _synctex_new_input(synctex_scanner_p scanner) { + if (scanner) { + synctex_node_p node = _synctex_malloc(sizeof(synctex_input_s)); + if (node) { + node->class_ = scanner->class_+synctex_node_type_input; + SYNCTEX_DID_NEW(node); + SYNCTEX_IMPLEMENT_CHARINDEX(node,0); + SYNCTEX_REGISTER_HANDLE_TO(node); + } + return node; + } + return NULL; +} + +static void _synctex_free_input(synctex_node_p node){ + if (node) { + SYNCTEX_SCANNER_REMOVE_HANDLE_TO(node); + SYNCTEX_WILL_FREE(node); + synctex_node_free(__synctex_tree_sibling(node)); + _synctex_free(_synctex_data_name(node)); + _synctex_free(node); + } +} + +/* The sheet is a first level node. + * It has no parent (the owner is the scanner itself) + * Its sibling points to another sheet. + * Its child points to its first child, in general a box. + * A sheet node contains only one synctex information: the page. + * This is the 1 based page index as given by TeX. + */ + +# ifdef SYNCTEX_NOTHING +# pragma mark sheet. +# endif +/** + * Every node has the same structure, but not the same size. + */ + +DEFINE_SYNCTEX_DATA_INT_GETSET_DECODE(page) + +typedef struct { + SYNCTEX_DECLARE_CHARINDEX + synctex_class_p class_; + synctex_data_u data[synctex_tree_scn_sheet_max+synctex_data_p_sheet_max]; +} synctex_node_sheet_s; + +/* sheet node creator */ + +#define DEFINE_synctex_new_scanned_NODE(NAME)\ +static synctex_node_p _synctex_new_##NAME(synctex_scanner_p scanner) {\ + if (scanner) {\ + ++SYNCTEX_CUR;\ + synctex_node_p node = _synctex_malloc(sizeof(synctex_node_##NAME##_s));\ + if (node) {\ + node->class_ = scanner->class_+synctex_node_type_##NAME;\ + SYNCTEX_DID_NEW(node); \ + SYNCTEX_IMPLEMENT_CHARINDEX(node,-1);\ + SYNCTEX_REGISTER_HANDLE_TO(node); \ + }\ + return node;\ + }\ + return NULL;\ +} +/* NB: -1 in SYNCTEX_IMPLEMENT_CHARINDEX above because + * the first char of the line has been scanned + */ +DEFINE_synctex_new_scanned_NODE(sheet) +static void _synctex_log_sheet(synctex_node_p node); +static char * _synctex_abstract_sheet(synctex_node_p node); +static void _synctex_display_sheet(synctex_node_p node); + +static const synctex_tree_model_s synctex_tree_model_sheet = { + synctex_tree_sibling_idx, /* sibling */ + -1, /* parent */ + synctex_tree_s_child_idx, /* child */ + -1, /* friend */ + -1, /* last */ + synctex_tree_sc_next_hbox_idx, /* next_hbox */ + -1, /* arg_sibling */ + -1, /* target */ + synctex_tree_scn_sheet_max +}; +static const synctex_data_model_s synctex_data_model_sheet = { + -1, /* tag */ + -1, /* line */ + -1, /* column */ + -1, /* h */ + -1, /* v */ + -1, /* width */ + -1, /* height */ + -1, /* depth */ + -1, /* mean_line */ + -1, /* weight */ + -1, /* h_V */ + -1, /* v_V */ + -1, /* width_V */ + -1, /* height_V */ + -1, /* depth_V */ + -1, /* name */ + synctex_data_sheet_page_idx, /* page */ + synctex_data_p_sheet_max +}; +static synctex_class_s synctex_class_sheet = { + NULL, /* No scanner yet */ + synctex_node_type_sheet, /* Node type */ + &_synctex_new_sheet, /* creator */ + &_synctex_free_node, /* destructor */ + &_synctex_log_sheet, /* log */ + &_synctex_display_sheet, /* display */ + &_synctex_abstract_sheet, /* abstract */ + &synctex_tree_model_sheet, /* tree model */ + &synctex_data_model_sheet, /* data model */ + &synctex_tlcpector_none, /* tlcpector */ + &synctex_inspector_none, /* inspector */ + &synctex_vispector_none, /* vispector */ +}; + +# ifdef SYNCTEX_NOTHING +# pragma mark form. +# endif +/** + * Every node has the same structure, but not the same size. + */ +typedef struct { + SYNCTEX_DECLARE_CHARINDEX + synctex_class_p class_; + synctex_data_u data[synctex_tree_sct_form_max+synctex_data_t_form_max]; +} synctex_node_form_s; + +DEFINE_synctex_new_scanned_NODE(form) + +static char * _synctex_abstract_form(synctex_node_p node); +static void _synctex_display_form(synctex_node_p node); +static void _synctex_log_form(synctex_node_p node); + +static const synctex_tree_model_s synctex_tree_model_form = { + synctex_tree_sibling_idx, /* sibling */ + -1, /* parent */ + synctex_tree_s_child_idx, /* child */ + -1, /* friend */ + -1, /* last */ + -1, /* next_hbox */ + -1, /* arg_sibling */ + synctex_tree_sc_target_idx, /* target */ + synctex_tree_sct_form_max +}; +static const synctex_data_model_s synctex_data_model_form = { + synctex_data_form_tag_idx, /* tag */ + -1, /* line */ + -1, /* column */ + -1, /* h */ + -1, /* v */ + -1, /* width */ + -1, /* height */ + -1, /* depth */ + -1, /* mean_line */ + -1, /* weight */ + -1, /* h_V */ + -1, /* v_V */ + -1, /* width_V */ + -1, /* height_V */ + -1, /* depth_V */ + -1, /* name */ + -1, /* page */ + synctex_data_t_form_max +}; +static synctex_class_s synctex_class_form = { + NULL, /* No scanner yet */ + synctex_node_type_form, /* Node type */ + &_synctex_new_form, /* creator */ + &_synctex_free_node, /* destructor */ + &_synctex_log_form, /* log */ + &_synctex_display_form, /* display */ + &_synctex_abstract_form, /* abstract */ + &synctex_tree_model_form, /* tree model */ + &synctex_data_model_form, /* data model */ + &synctex_tlcpector_none, /* tlcpector */ + &synctex_inspector_none, /* inspector */ + &synctex_vispector_none, /* vispector */ +}; + +# ifdef SYNCTEX_NOTHING +# pragma mark vbox. +# endif + +/* A box node contains navigation and synctex information + * There are different kinds of boxes. + * Only horizontal boxes are treated differently because of their visible size. + */ +typedef struct { + SYNCTEX_DECLARE_CHARINDEX + synctex_class_p class_; + synctex_data_u data[synctex_tree_spcfl_vbox_max+synctex_data_box_max]; +} synctex_node_vbox_s; + +/* vertical box node creator */ +DEFINE_synctex_new_scanned_NODE(vbox) + +static char * _synctex_abstract_vbox(synctex_node_p node); +static void _synctex_display_vbox(synctex_node_p node); +static void _synctex_log_vbox(synctex_node_p node); + +static const synctex_tree_model_s synctex_tree_model_vbox = { + synctex_tree_sibling_idx, /* sibling */ + synctex_tree_s_parent_idx, /* parent */ + synctex_tree_sp_child_idx, /* child */ + synctex_tree_spc_friend_idx, /* friend */ + synctex_tree_spcf_last_idx, /* last */ + -1, /* next_hbox */ + -1, /* arg_sibling */ + -1, /* target */ + synctex_tree_spcfl_vbox_max +}; + +#define SYNCTEX_DFLT_COLUMN -1 + +DEFINE_SYNCTEX_DATA_INT_GETSET(column) +static synctex_status_t _synctex_data_decode_column(synctex_node_p node) { + if (_synctex_data_has_column(node)) { + synctex_is_s is = _synctex_decode_int_opt(node->class_->scanner, + SYNCTEX_DFLT_COLUMN); + if (is.status == SYNCTEX_STATUS_OK) { + _synctex_data_set_column(node,is.integer); + } + return is.status; + } + return SYNCTEX_STATUS_BAD_ARGUMENT; +} +DEFINE_SYNCTEX_DATA_INT_GETSET_DECODE(h) +DEFINE_SYNCTEX_DATA_INT_GETSET_DECODE_v(v) +DEFINE_SYNCTEX_DATA_INT_GETSET_DECODE(width) +DEFINE_SYNCTEX_DATA_INT_GETSET_DECODE(height) +DEFINE_SYNCTEX_DATA_INT_GETSET_DECODE(depth) + +SYNCTEX_INLINE static void _synctex_data_set_tlc(synctex_node_p node, synctex_node_p model) { + _synctex_data_set_tag(node, _synctex_data_tag(model)); + _synctex_data_set_line(node, _synctex_data_line(model)); + _synctex_data_set_column(node, _synctex_data_column(model)); +} +SYNCTEX_INLINE static void _synctex_data_set_tlchv(synctex_node_p node, synctex_node_p model) { + _synctex_data_set_tlc(node,model); + _synctex_data_set_h(node, _synctex_data_h(model)); + _synctex_data_set_v(node, _synctex_data_v(model)); +} + +static const synctex_data_model_s synctex_data_model_box = { + synctex_data_tag_idx, /* tag */ + synctex_data_line_idx, /* line */ + synctex_data_column_idx,/* column */ + synctex_data_h_idx, /* h */ + synctex_data_v_idx, /* v */ + synctex_data_width_idx, /* width */ + synctex_data_height_idx,/* height */ + synctex_data_depth_idx, /* depth */ + -1, /* mean_line */ + -1, /* weight */ + -1, /* h_V */ + -1, /* v_V */ + -1, /* width_V */ + -1, /* height_V */ + -1, /* depth_V */ + -1, /* name */ + -1, /* page */ + synctex_data_box_max +}; +static const synctex_tlcpector_s synctex_tlcpector_default = { + &_synctex_data_tag, /* tag */ + &_synctex_data_line, /* line */ + &_synctex_data_column, /* column */ +}; +static const synctex_inspector_s synctex_inspector_box = { + &_synctex_data_h, + &_synctex_data_v, + &_synctex_data_width, + &_synctex_data_height, + &_synctex_data_depth, +}; +static float __synctex_node_visible_h(synctex_node_p node); +static float __synctex_node_visible_v(synctex_node_p node); +static float __synctex_node_visible_width(synctex_node_p node); +static float __synctex_node_visible_height(synctex_node_p node); +static float __synctex_node_visible_depth(synctex_node_p node); +static synctex_vispector_s synctex_vispector_box = { + &__synctex_node_visible_h, + &__synctex_node_visible_v, + &__synctex_node_visible_width, + &__synctex_node_visible_height, + &__synctex_node_visible_depth, +}; +/* These are static class objects, each scanner will make a copy of them and setup the scanner field. + */ +static synctex_class_s synctex_class_vbox = { + NULL, /* No scanner yet */ + synctex_node_type_vbox, /* Node type */ + &_synctex_new_vbox, /* creator */ + &_synctex_free_node, /* destructor */ + &_synctex_log_vbox, /* log */ + &_synctex_display_vbox, /* display */ + &_synctex_abstract_vbox, /* abstract */ + &synctex_tree_model_vbox, /* tree model */ + &synctex_data_model_box, /* data model */ + &synctex_tlcpector_default, /* tlcpector */ + &synctex_inspector_box, /* inspector */ + &synctex_vispector_box, /* vispector */ +}; + +# ifdef SYNCTEX_NOTHING +# pragma mark hbox. +# endif + +/* Horizontal boxes must contain visible size, because 0 width does not mean emptiness. + * They also contain an average of the line numbers of the containing nodes. */ + +static const synctex_tree_model_s synctex_tree_model_hbox = { + synctex_tree_sibling_idx, /* sibling */ + synctex_tree_s_parent_idx, /* parent */ + synctex_tree_sp_child_idx, /* child */ + synctex_tree_spc_friend_idx, /* friend */ + synctex_tree_spcf_last_idx, /* last */ + synctex_tree_spcfl_next_hbox_idx, /* next_hbox */ + -1, /* arg_sibling */ + -1, /* target */ + synctex_tree_spcfln_hbox_max +}; + +DEFINE_SYNCTEX_DATA_INT_GETSET(mean_line) +DEFINE_SYNCTEX_DATA_INT_GETSET(weight) +DEFINE_SYNCTEX_DATA_INT_GETSET(h_V) +DEFINE_SYNCTEX_DATA_INT_GETSET(v_V) +DEFINE_SYNCTEX_DATA_INT_GETSET(width_V) +DEFINE_SYNCTEX_DATA_INT_GETSET(height_V) +DEFINE_SYNCTEX_DATA_INT_GETSET(depth_V) + +/** + * The hbox model. + * It contains V variants of geometrical information. + * It happens that hboxes contain material that is not used to compute + * the bounding box. Some letters may appear out of the box given by TeX. + * In such a situation, the visible bounding box is bigger ence the V variant. + * Only hboxes have such variant. It does not make sense for void boxes + * and it is not used here for vboxes. + * - author: JL + */ + +static const synctex_data_model_s synctex_data_model_hbox = { + synctex_data_tag_idx, /* tag */ + synctex_data_line_idx, /* line */ + synctex_data_column_idx,/* column */ + synctex_data_h_idx, /* h */ + synctex_data_v_idx, /* v */ + synctex_data_width_idx, /* width */ + synctex_data_height_idx,/* height */ + synctex_data_depth_idx, /* depth */ + synctex_data_mean_line_idx, /* mean_line */ + synctex_data_weight_idx, /* weight */ + synctex_data_h_V_idx, /* h_V */ + synctex_data_v_V_idx, /* v_V */ + synctex_data_width_V_idx, /* width_V */ + synctex_data_height_V_idx, /* height_V */ + synctex_data_depth_V_idx, /* depth_V */ + -1, /* name */ + -1, /* page */ + synctex_data_hbox_max +}; + +typedef struct { + SYNCTEX_DECLARE_CHARINDEX + synctex_class_p class_; + synctex_data_u data[synctex_tree_spcfln_hbox_max+synctex_data_hbox_max]; +} synctex_node_hbox_s; + +/* horizontal box node creator */ +DEFINE_synctex_new_scanned_NODE(hbox) + +static void _synctex_log_hbox(synctex_node_p node); +static char * _synctex_abstract_hbox(synctex_node_p node); +static void _synctex_display_hbox(synctex_node_p node); + +static synctex_class_s synctex_class_hbox = { + NULL, /* No scanner yet */ + synctex_node_type_hbox, /* Node type */ + &_synctex_new_hbox, /* creator */ + &_synctex_free_node, /* destructor */ + &_synctex_log_hbox, /* log */ + &_synctex_display_hbox, /* display */ + &_synctex_abstract_hbox, /* abstract */ + &synctex_tree_model_hbox, /* tree model */ + &synctex_data_model_hbox, /* data model */ + &synctex_tlcpector_default, /* tlcpector */ + &synctex_inspector_box, /* inspector */ + &synctex_vispector_box, /* vispector */ +}; + +# ifdef SYNCTEX_NOTHING +# pragma mark void vbox. +# endif + +/* This void box node implementation is either horizontal or vertical + * It does not contain a child field. + */ +static const synctex_tree_model_s synctex_tree_model_spf = { + synctex_tree_sibling_idx, /* sibling */ + synctex_tree_s_parent_idx, /* parent */ + -1, /* child */ + synctex_tree_sp_friend_idx, /* friend */ + -1, /* last */ + -1, /* next_hbox */ + -1, /* arg_sibling */ + -1, /* target */ + synctex_tree_spf_max +}; +typedef struct { + SYNCTEX_DECLARE_CHARINDEX + synctex_class_p class_; + synctex_data_u data[synctex_tree_spf_max+synctex_data_box_max]; +} synctex_node_void_vbox_s; + +/* vertical void box node creator */ +DEFINE_synctex_new_scanned_NODE(void_vbox) + +static void _synctex_log_void_box(synctex_node_p node); +static char * _synctex_abstract_void_vbox(synctex_node_p node); +static void _synctex_display_void_vbox(synctex_node_p node); + +static synctex_class_s synctex_class_void_vbox = { + NULL, /* No scanner yet */ + synctex_node_type_void_vbox,/* Node type */ + &_synctex_new_void_vbox, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_void_box, /* log */ + &_synctex_display_void_vbox,/* display */ + &_synctex_abstract_void_vbox,/* abstract */ + &synctex_tree_model_spf, /* tree model */ + &synctex_data_model_box, /* data model */ + &synctex_tlcpector_default, /* tlcpector */ + &synctex_inspector_box, /* inspector */ + &synctex_vispector_box, /* vispector */ +}; + +# ifdef SYNCTEX_NOTHING +# pragma mark void hbox. +# endif + +typedef synctex_node_void_vbox_s synctex_node_void_hbox_s; + +/* horizontal void box node creator */ +DEFINE_synctex_new_scanned_NODE(void_hbox) + +static char * _synctex_abstract_void_hbox(synctex_node_p node); +static void _synctex_display_void_hbox(synctex_node_p node); + +static synctex_class_s synctex_class_void_hbox = { + NULL, /* No scanner yet */ + synctex_node_type_void_hbox,/* Node type */ + &_synctex_new_void_hbox, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_void_box, /* log */ + &_synctex_display_void_hbox,/* display */ + &_synctex_abstract_void_hbox,/* abstract */ + &synctex_tree_model_spf, /* tree model */ + &synctex_data_model_box, /* data model */ + &synctex_tlcpector_default, /* tlcpector */ + &synctex_inspector_box, /* inspector */ + &synctex_vispector_box, /* vispector */ +}; + +# ifdef SYNCTEX_NOTHING +# pragma mark form ref. +# endif + +/* The form ref node. */ +typedef struct { + SYNCTEX_DECLARE_CHARINDEX + synctex_class_p class_; + synctex_data_u data[synctex_tree_spfa_max+synctex_data_ref_thv_max]; +} synctex_node_ref_s; + +/* form ref node creator */ +DEFINE_synctex_new_scanned_NODE(ref) + +static void _synctex_log_ref(synctex_node_p node); +static char * _synctex_abstract_ref(synctex_node_p node); +static void _synctex_display_ref(synctex_node_p node); + +static const synctex_tree_model_s synctex_tree_model_spfa = { + synctex_tree_sibling_idx, /* sibling */ + synctex_tree_s_parent_idx, /* parent */ + -1, /* child */ + synctex_tree_sp_friend_idx, /* friend */ + -1, /* last */ + -1, /* next_hbox */ + synctex_tree_spf_arg_sibling_idx, /* arg_sibling */ + -1, /* target */ + synctex_tree_spfa_max +}; +static const synctex_data_model_s synctex_data_model_ref = { + synctex_data_tag_idx, /* tag */ + -1, /* line */ + -1, /* column */ + synctex_data_ref_h_idx, /* h */ + synctex_data_ref_v_idx, /* v */ + -1, /* width */ + -1, /* height */ + -1, /* depth */ + -1, /* mean_line */ + -1, /* weight */ + -1, /* h_V */ + -1, /* v_V */ + -1, /* width_V */ + -1, /* height_V */ + -1, /* depth_V */ + -1, /* name */ + -1, /* page */ + synctex_data_ref_thv_max /* size */ +}; +static synctex_class_s synctex_class_ref = { + NULL, /* No scanner yet */ + synctex_node_type_ref, /* Node type */ + &_synctex_new_ref, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_ref, /* log */ + &_synctex_display_ref, /* display */ + &_synctex_abstract_ref, /* abstract */ + &synctex_tree_model_spfa, /* navigator */ + &synctex_data_model_ref, /* data model */ + &synctex_tlcpector_none, /* tlcpector */ + &synctex_inspector_none, /* inspector */ + &synctex_vispector_none, /* vispector */ +}; +# ifdef SYNCTEX_NOTHING +# pragma mark small node. +# endif + +/* The small nodes correspond to glue, penalty, math and boundary nodes. */ +static const synctex_data_model_s synctex_data_model_tlchv = { + synctex_data_tag_idx, /* tag */ + synctex_data_line_idx, /* line */ + synctex_data_column_idx, /* column */ + synctex_data_h_idx, /* h */ + synctex_data_v_idx, /* v */ + -1, /* width */ + -1, /* height */ + -1, /* depth */ + -1, /* mean_line */ + -1, /* weight */ + -1, /* h_V */ + -1, /* v_V */ + -1, /* width_V */ + -1, /* height_V */ + -1, /* depth_V */ + -1, /* name */ + -1, /* page */ + synctex_data_tlchv_max +}; + +typedef struct { + SYNCTEX_DECLARE_CHARINDEX + synctex_class_p class_; + synctex_data_u data[synctex_tree_spf_max+synctex_data_tlchv_max]; +} synctex_node_tlchv_s; + +static void _synctex_log_tlchv_node(synctex_node_p node); + +# ifdef SYNCTEX_NOTHING +# pragma mark math. +# endif + +typedef synctex_node_tlchv_s synctex_node_math_s; + +/* math node creator */ +DEFINE_synctex_new_scanned_NODE(math) + +static char * _synctex_abstract_math(synctex_node_p node); +static void _synctex_display_math(synctex_node_p node); +static synctex_inspector_s synctex_inspector_hv = { + &_synctex_data_h, + &_synctex_data_v, + &_synctex_int_none, + &_synctex_int_none, + &_synctex_int_none, +}; +static synctex_vispector_s synctex_vispector_hv = { + &__synctex_node_visible_h, + &__synctex_node_visible_v, + &_synctex_float_none, + &_synctex_float_none, + &_synctex_float_none, +}; + +static synctex_class_s synctex_class_math = { + NULL, /* No scanner yet */ + synctex_node_type_math, /* Node type */ + &_synctex_new_math, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_tlchv_node, /* log */ + &_synctex_display_math, /* display */ + &_synctex_abstract_math, /* abstract */ + &synctex_tree_model_spf, /* tree model */ + &synctex_data_model_tlchv, /* data model */ + &synctex_tlcpector_default, /* tlcpector */ + &synctex_inspector_hv, /* inspector */ + &synctex_vispector_hv, /* vispector */ +}; + +# ifdef SYNCTEX_NOTHING +# pragma mark kern node. +# endif + +static const synctex_data_model_s synctex_data_model_tlchvw = { + synctex_data_tag_idx, /* tag */ + synctex_data_line_idx, /* line */ + synctex_data_column_idx,/* column */ + synctex_data_h_idx, /* h */ + synctex_data_v_idx, /* v */ + synctex_data_width_idx, /* width */ + -1, /* height */ + -1, /* depth */ + -1, /* mean_line */ + -1, /* weight */ + -1, /* h_V */ + -1, /* v_V */ + -1, /* width_V */ + -1, /* height_V */ + -1, /* depth_V */ + -1, /* name */ + -1, /* page */ + synctex_data_tlchvw_max +}; +typedef struct { + SYNCTEX_DECLARE_CHARINDEX + synctex_class_p class_; + synctex_data_u data[synctex_tree_spf_max+synctex_data_tlchvw_max]; +} synctex_node_kern_s; + +/* kern node creator */ +DEFINE_synctex_new_scanned_NODE(kern) + +static void _synctex_log_kern_node(synctex_node_p node); +static char * _synctex_abstract_kern(synctex_node_p node); +static void _synctex_display_kern(synctex_node_p node); + +static synctex_inspector_s synctex_inspector_kern = { + &_synctex_data_h, + &_synctex_data_v, + &_synctex_data_width, + &_synctex_int_none, + &_synctex_int_none, +}; +static float __synctex_kern_visible_h(synctex_node_p node); +static float __synctex_kern_visible_width(synctex_node_p node); +static synctex_vispector_s synctex_vispector_kern = { + &__synctex_kern_visible_h, + &__synctex_node_visible_v, + &__synctex_kern_visible_width, + &_synctex_float_none, + &_synctex_float_none, +}; + +static synctex_class_s synctex_class_kern = { + NULL, /* No scanner yet */ + synctex_node_type_kern, /* Node type */ + &_synctex_new_kern, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_kern_node, /* log */ + &_synctex_display_kern, /* display */ + &_synctex_abstract_kern, /* abstract */ + &synctex_tree_model_spf, /* tree model */ + &synctex_data_model_tlchvw, /* data model */ + &synctex_tlcpector_default, /* tlcpector */ + &synctex_inspector_kern, /* inspector */ + &synctex_vispector_kern, /* vispector */ +}; + +# ifdef SYNCTEX_NOTHING +# pragma mark glue. +# endif + +/* glue node creator */ +typedef synctex_node_tlchv_s synctex_node_glue_s; +DEFINE_synctex_new_scanned_NODE(glue) + +static char * _synctex_abstract_glue(synctex_node_p node); +static void _synctex_display_glue(synctex_node_p node); + +static synctex_class_s synctex_class_glue = { + NULL, /* No scanner yet */ + synctex_node_type_glue, /* Node type */ + &_synctex_new_glue, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_tlchv_node, /* log */ + &_synctex_display_glue, /* display */ + &_synctex_abstract_glue, /* abstract */ + &synctex_tree_model_spf, /* tree model */ + &synctex_data_model_tlchv, /* data model */ + &synctex_tlcpector_default, /* tlcpector */ + &synctex_inspector_hv, /* inspector */ + &synctex_vispector_hv, /* vispector */ +}; + +/* The small nodes correspond to glue and boundary nodes. */ + +# ifdef SYNCTEX_NOTHING +# pragma mark rule. +# endif + +typedef struct { + SYNCTEX_DECLARE_CHARINDEX + synctex_class_p class_; + synctex_data_u data[synctex_tree_spf_max+synctex_data_box_max]; +} synctex_node_rule_s; + +DEFINE_synctex_new_scanned_NODE(rule) + +static void _synctex_log_rule(synctex_node_p node); +static char * _synctex_abstract_rule(synctex_node_p node); +static void _synctex_display_rule(synctex_node_p node); + +static float __synctex_rule_visible_h(synctex_node_p node); +static float __synctex_rule_visible_v(synctex_node_p node); +static float __synctex_rule_visible_width(synctex_node_p node); +static float __synctex_rule_visible_height(synctex_node_p node); +static float __synctex_rule_visible_depth(synctex_node_p node); +static synctex_vispector_s synctex_vispector_rule = { + &__synctex_rule_visible_h, + &__synctex_rule_visible_v, + &__synctex_rule_visible_width, + &__synctex_rule_visible_height, + &__synctex_rule_visible_depth, +}; + +static synctex_class_s synctex_class_rule = { + NULL, /* No scanner yet */ + synctex_node_type_rule, /* Node type */ + &_synctex_new_rule, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_rule, /* log */ + &_synctex_display_rule, /* display */ + &_synctex_abstract_rule, /* abstract */ + &synctex_tree_model_spf, /* tree model */ + &synctex_data_model_box, /* data model */ + &synctex_tlcpector_default, /* tlcpector */ + &synctex_inspector_box, /* inspector */ + &synctex_vispector_rule, /* vispector */ +}; + +# ifdef SYNCTEX_NOTHING +# pragma mark boundary. +# endif + +/* boundary node creator */ +typedef synctex_node_tlchv_s synctex_node_boundary_s; +DEFINE_synctex_new_scanned_NODE(boundary) + +static char * _synctex_abstract_boundary(synctex_node_p node); +static void _synctex_display_boundary(synctex_node_p node); + +static synctex_class_s synctex_class_boundary = { + NULL, /* No scanner yet */ + synctex_node_type_boundary, /* Node type */ + &_synctex_new_boundary, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_tlchv_node, /* log */ + &_synctex_display_boundary, /* display */ + &_synctex_abstract_boundary,/* abstract */ + &synctex_tree_model_spf, /* tree model */ + &synctex_data_model_tlchv, /* data model */ + &synctex_tlcpector_default, /* tlcpector */ + &synctex_inspector_hv, /* inspector */ + &synctex_vispector_hv, /* vispector */ +}; + +# ifdef SYNCTEX_NOTHING +# pragma mark box boundary. +# endif + +typedef struct { + SYNCTEX_DECLARE_CHARINDEX + synctex_class_p class_; + synctex_data_u data[synctex_tree_spfa_max+synctex_data_tlchv_max]; +} synctex_node_box_bdry_s; + +#define DEFINE_synctex_new_unscanned_NODE(NAME)\ +SYNCTEX_INLINE static synctex_node_p _synctex_new_##NAME(synctex_scanner_p scanner) {\ + if (scanner) {\ + synctex_node_p node = _synctex_malloc(sizeof(synctex_node_##NAME##_s));\ + if (node) {\ + node->class_ = scanner->class_+synctex_node_type_##NAME;\ + SYNCTEX_DID_NEW(node); \ + }\ + return node;\ + }\ + return NULL;\ +} +DEFINE_synctex_new_unscanned_NODE(box_bdry) + +static char * _synctex_abstract_box_bdry(synctex_node_p node); +static void _synctex_display_box_bdry(synctex_node_p node); + +static synctex_class_s synctex_class_box_bdry = { + NULL, /* No scanner yet */ + synctex_node_type_box_bdry, /* Node type */ + &_synctex_new_box_bdry, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_tlchv_node, /* log */ + &_synctex_display_box_bdry, /* display */ + &_synctex_abstract_box_bdry,/* display */ + &synctex_tree_model_spfa, /* tree model */ + &synctex_data_model_tlchv, /* data model */ + &synctex_tlcpector_default, /* tlcpector */ + &synctex_inspector_hv, /* inspector */ + &synctex_vispector_hv, /* vispector */ +}; + +# ifdef SYNCTEX_NOTHING +# pragma mark hbox proxy. +# endif + +/** + * Standard nodes refer to TeX nodes: math, kern, boxes... + * Proxy nodes are used to support forms. + * A form is parsed as a tree of standard nodes starting + * at the top left position. + * When a reference is used, the form is duplicated + * to the location specified by the reference. + * As the same form can be duplicated at different locations, + * the geometrical information is relative to its own top left point. + * As we need absolute locations, we use proxy nodes. + * A proxy node records an offset and the target node. + * The target partly acts as a delegate. + * The h and v position of the proxy node is the h and v + * position of the target shifted by the proxy's offset. + * The width, height and depth are not sensitive to offsets. + * When are proxies created ? + * 1) when the synctex file has been parsed, all the form refs + * are replaced by proxies to the content of a form. + * This content is a node with siblings (actually none). + * Those root proxies have the parent of the ref they replace, + * so their parents exist and are no proxy. + * Moreover, if they have no sibling, it means that their target have no + * sibling as well. + * Such nodes are called root proxies. + * 2) On the fly, when a proxy is asked for its child + * (or sibling) and has none, a proxy to its target's child + * (or sibling) is created if any. There are only 2 possible situations: + * either the newly created proxy is the child of a proxy, + * or it is the sibling of a proxy created on the fly. + * In both cases, the parent is a proxy with children. + * Such nodes are called child proxies. + * How to compute the offset of a proxy ? + * The offset of root proxy objects is exactly + * the offset of the ref they replace. + * The offset of other proxies is their owner's, + * except when pointing to a root proxy. + * What happens for cascading forms ? + * Here is an example diagram + * + * At parse time, the arrow means "owns": + * sheet0 -> ref_to1 + * + * target1 -> ref_to2 + * + * target2 -> child22 + * + * After replacing the refs: + * sheet0 -> proxy00 -> proxy01 -> proxy02 + * | | | + * target1 -> proxy11 -> proxy12 + * | | + * target2 -> proxy22 + * + * proxy00, proxy11 and proxy22 are root proxies. + * Their offset is the one of the ref they replace + * proxy01, proxy02 and proxy12 are child proxies. + * Their proxy is the one of their parent. + * Optimization. + * After all the refs are replaced, there are only root nodes + * targeting standard node. We make sure that each child proxy + * also targets a standard node. + * It is possible for a proxy to have a standard sibling + * whereas its target has no sibling at all. Root proxies + * are such nodes, and are the only ones. + * The consequence is that proxies created on the fly + * must take into account this situation. + */ + +/* A proxy to a hbox. + * A proxy do have a target, which can be a proxy + */ + +static const synctex_tree_model_s synctex_tree_model_proxy_hbox = { + synctex_tree_sibling_idx, /* sibling */ + synctex_tree_s_parent_idx, /* parent */ + synctex_tree_sp_child_idx, /* child */ + synctex_tree_spc_friend_idx, /* friend */ + synctex_tree_spcf_last_idx, /* last */ + synctex_tree_spcfl_next_hbox_idx, /* next_hbox */ + -1, /* arg_sibling */ + synctex_tree_spcfln_target_idx, /* target */ + synctex_tree_spcflnt_proxy_hbox_max +}; +static const synctex_data_model_s synctex_data_model_proxy = { + -1, /* tag */ + -1, /* line */ + -1, /* column */ + synctex_data_proxy_h_idx, /* h */ + synctex_data_proxy_v_idx, /* v */ + -1, /* width */ + -1, /* height */ + -1, /* depth */ + -1, /* mean_line */ + -1, /* weight */ + -1, /* h_V */ + -1, /* v_V */ + -1, /* width_V */ + -1, /* height_V */ + -1, /* depth_V */ + -1, /* name */ + -1, /* page */ + synctex_data_proxy_hv_max +}; +typedef struct { + SYNCTEX_DECLARE_CHARINDEX + synctex_class_p class_; + synctex_data_u data[synctex_tree_spcflnt_proxy_hbox_max+synctex_data_proxy_hv_max]; +} synctex_node_proxy_hbox_s; + +/* box proxy node creator */ +DEFINE_synctex_new_unscanned_NODE(proxy_hbox) + +static void _synctex_log_proxy(synctex_node_p node); +static char * _synctex_abstract_proxy_hbox(synctex_node_p node); +static void _synctex_display_proxy_hbox(synctex_node_p node); + +static int _synctex_proxy_tag(synctex_node_p); +static int _synctex_proxy_line(synctex_node_p); +static int _synctex_proxy_column(synctex_node_p); + +static synctex_tlcpector_s synctex_tlcpector_proxy = { + &_synctex_proxy_tag, + &_synctex_proxy_line, + &_synctex_proxy_column, +}; +static int _synctex_proxy_h(synctex_node_p); +static int _synctex_proxy_v(synctex_node_p); +static int _synctex_proxy_width(synctex_node_p); +static int _synctex_proxy_height(synctex_node_p); +static int _synctex_proxy_depth(synctex_node_p); +static synctex_inspector_s synctex_inspector_proxy_box = { + &_synctex_proxy_h, + &_synctex_proxy_v, + &_synctex_proxy_width, + &_synctex_proxy_height, + &_synctex_proxy_depth, +}; + +static float __synctex_proxy_visible_h(synctex_node_p); +static float __synctex_proxy_visible_v(synctex_node_p); +static float __synctex_proxy_visible_width(synctex_node_p); +static float __synctex_proxy_visible_height(synctex_node_p); +static float __synctex_proxy_visible_depth(synctex_node_p); + +static synctex_vispector_s synctex_vispector_proxy_box = { + &__synctex_proxy_visible_h, + &__synctex_proxy_visible_v, + &__synctex_proxy_visible_width, + &__synctex_proxy_visible_height, + &__synctex_proxy_visible_depth, +}; + +static synctex_class_s synctex_class_proxy_hbox = { + NULL, /* No scanner yet */ + synctex_node_type_proxy_hbox, /* Node type */ + &_synctex_new_proxy_hbox, /* creator */ + &_synctex_free_node, /* destructor */ + &_synctex_log_proxy, /* log */ + &_synctex_display_proxy_hbox, /* display */ + &_synctex_abstract_proxy_hbox, /* abstract */ + &synctex_tree_model_proxy_hbox, /* tree model */ + &synctex_data_model_proxy, /* data model */ + &synctex_tlcpector_proxy, /* tlcpector */ + &synctex_inspector_proxy_box, /* inspector */ + &synctex_vispector_proxy_box, /* vispector */ +}; + +# ifdef SYNCTEX_NOTHING +# pragma mark vbox proxy. +# endif + +/* A proxy to a vbox. */ + +static const synctex_tree_model_s synctex_tree_model_proxy_vbox = { + synctex_tree_sibling_idx, /* sibling */ + synctex_tree_s_parent_idx, /* parent */ + synctex_tree_sp_child_idx, /* child */ + synctex_tree_spc_friend_idx, /* friend */ + synctex_tree_spcf_last_idx, /* last */ + -1, /* next_hbox */ + -1, /* arg_sibling */ + synctex_tree_spcfl_target_idx, /* target */ + synctex_tree_spcflt_proxy_vbox_max +}; + +typedef struct { + SYNCTEX_DECLARE_CHARINDEX + synctex_class_p class_; + synctex_data_u data[synctex_tree_spcflt_proxy_vbox_max+synctex_data_proxy_hv_max]; +} synctex_node_proxy_vbox_s; + +/* box proxy node creator */ +DEFINE_synctex_new_unscanned_NODE(proxy_vbox) + +static void _synctex_log_proxy(synctex_node_p node); +static char * _synctex_abstract_proxy_vbox(synctex_node_p node); +static void _synctex_display_proxy_vbox(synctex_node_p node); + +static synctex_class_s synctex_class_proxy_vbox = { + NULL, /* No scanner yet */ + synctex_node_type_proxy_vbox, /* Node type */ + &_synctex_new_proxy_vbox, /* creator */ + &_synctex_free_node, /* destructor */ + &_synctex_log_proxy, /* log */ + &_synctex_display_proxy_vbox, /* display */ + &_synctex_abstract_proxy_vbox, /* abstract */ + &synctex_tree_model_proxy_vbox, /* tree model */ + &synctex_data_model_proxy, /* data model */ + &synctex_tlcpector_proxy, /* tlcpector */ + &synctex_inspector_proxy_box, /* inspector */ + &synctex_vispector_proxy_box, /* vispector */ +}; + +# ifdef SYNCTEX_NOTHING +# pragma mark proxy. +# endif + +/** + * A proxy to a node but a box. + */ + +static const synctex_tree_model_s synctex_tree_model_proxy = { + synctex_tree_sibling_idx, /* sibling */ + synctex_tree_s_parent_idx, /* parent */ + -1, /* child */ + synctex_tree_sp_friend_idx, /* friend */ + -1, /* last */ + -1, /* next_hbox */ + -1, /* arg_sibling */ + synctex_tree_spf_target_idx,/* target */ + synctex_tree_spft_proxy_max +}; + +typedef struct { + SYNCTEX_DECLARE_CHARINDEX + synctex_class_p class_; + synctex_data_u data[synctex_tree_spft_proxy_max+synctex_data_proxy_hv_max]; +} synctex_node_proxy_s; + +/* proxy node creator */ +DEFINE_synctex_new_unscanned_NODE(proxy) + +static void _synctex_log_proxy(synctex_node_p node); +static char * _synctex_abstract_proxy(synctex_node_p node); +static void _synctex_display_proxy(synctex_node_p node); + +static synctex_vispector_s synctex_vispector_proxy = { + &__synctex_proxy_visible_h, + &__synctex_proxy_visible_v, + &__synctex_proxy_visible_width, + &_synctex_float_none, + &_synctex_float_none, +}; + +static synctex_class_s synctex_class_proxy = { + NULL, /* No scanner yet */ + synctex_node_type_proxy, /* Node type */ + &_synctex_new_proxy, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_proxy, /* log */ + &_synctex_display_proxy, /* display */ + &_synctex_abstract_proxy, /* abstract */ + &synctex_tree_model_proxy, /* tree model */ + &synctex_data_model_proxy, /* data model */ + &synctex_tlcpector_proxy, /* tlcpector */ + &synctex_inspector_proxy_box, /* inspector */ + &synctex_vispector_proxy, /* vispector */ +}; + +# ifdef SYNCTEX_NOTHING +# pragma mark last proxy. +# endif + +/** + * A proxy to the last proxy/box boundary. + */ + +static const synctex_tree_model_s synctex_tree_model_proxy_last = { + synctex_tree_sibling_idx, /* sibling */ + synctex_tree_s_parent_idx, /* parent */ + -1, /* child */ + synctex_tree_sp_friend_idx, /* friend */ + -1, /* last */ + -1, /* next_hbox */ + synctex_tree_spf_arg_sibling_idx, /* arg_sibling */ + synctex_tree_spfa_target_idx, /* target */ + synctex_tree_spfat_proxy_last_max +}; + +typedef struct { + SYNCTEX_DECLARE_CHARINDEX + synctex_class_p class_; + synctex_data_u data[synctex_tree_spfat_proxy_last_max+synctex_data_proxy_hv_max]; +} synctex_node_proxy_last_s; + +/* proxy node creator */ +DEFINE_synctex_new_unscanned_NODE(proxy_last) + +static void _synctex_log_proxy(synctex_node_p node); +static char * _synctex_abstract_proxy(synctex_node_p node); +static void _synctex_display_proxy(synctex_node_p node); + +static synctex_class_s synctex_class_proxy_last = { + NULL, /* No scanner yet */ + synctex_node_type_proxy_last, /* Node type */ + &_synctex_new_proxy, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_proxy, /* log */ + &_synctex_display_proxy, /* display */ + &_synctex_abstract_proxy, /* abstract */ + &synctex_tree_model_proxy_last, /* tree model */ + &synctex_data_model_proxy, /* data model */ + &synctex_tlcpector_proxy, /* tlcpector */ + &synctex_inspector_proxy_box, /* inspector */ + &synctex_vispector_proxy, /* vispector */ +}; + +# ifdef SYNCTEX_NOTHING +# pragma mark handle. +# endif + +/** + * A handle node. + * A handle is never the target of a proxy + * or another handle. + * The child of a handle is always a handle if any. + * The sibling of a handle is always a handle if any. + * The parent of a handle is always a handle if any. + */ + +static const synctex_tree_model_s synctex_tree_model_handle = { + synctex_tree_sibling_idx, /* sibling */ + synctex_tree_s_parent_idx, /* parent */ + synctex_tree_sp_child_idx, /* child */ + -1, /* friend */ + -1, /* last */ + -1, /* next_hbox */ + -1, /* arg_sibling */ + synctex_tree_spc_target_idx,/* target */ + synctex_tree_spct_handle_max +}; + +static const synctex_data_model_s synctex_data_model_handle = { + -1, /* tag */ + -1, /* line */ + -1, /* column */ + -1, /* h */ + -1, /* v */ + -1, /* width */ + -1, /* height */ + -1, /* depth */ + -1, /* mean_line */ + synctex_data_handle_w_idx, /* weight */ + -1, /* h_V */ + -1, /* v_V */ + -1, /* width_V */ + -1, /* height_V */ + -1, /* depth_V */ + -1, /* name */ + -1, /* page */ + synctex_data_handle_w_max +}; + +typedef struct { + SYNCTEX_DECLARE_CHARINDEX + synctex_class_p class_; + synctex_data_u data[synctex_tree_spct_handle_max+synctex_data_handle_w_max]; +} synctex_node_handle_s; + +/* handle node creator */ +DEFINE_synctex_new_unscanned_NODE(handle) + +static void _synctex_log_handle(synctex_node_p node); +static char * _synctex_abstract_handle(synctex_node_p node); +static void _synctex_display_handle(synctex_node_p node); + +static synctex_class_s synctex_class_handle = { + NULL, /* No scanner yet */ + synctex_node_type_handle, /* Node type */ + &_synctex_new_handle, /* creator */ + &_synctex_free_handle, /* destructor */ + &_synctex_log_handle, /* log */ + &_synctex_display_handle, /* display */ + &_synctex_abstract_handle, /* abstract */ + &synctex_tree_model_handle, /* tree model */ + &synctex_data_model_handle, /* data model */ + &synctex_tlcpector_proxy, /* tlcpector */ + &synctex_inspector_proxy_box, /* inspector */ + &synctex_vispector_proxy_box, /* vispector */ +}; + +SYNCTEX_INLINE static synctex_node_p _synctex_new_handle_with_target(synctex_node_p target) { + if (target) { + synctex_node_p result = _synctex_new_handle(target->class_->scanner); + if (result) { + _synctex_tree_set_target(result,target); + return result; + } + } + return NULL; +} +SYNCTEX_INLINE static synctex_node_p _synctex_new_handle_with_child(synctex_node_p child) { + if (child) { + synctex_node_p result = _synctex_new_handle(child->class_->scanner); + if (result) { + _synctex_tree_set_child(result,child); + return result; + } + } + return NULL; +} + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Navigation +# endif +synctex_node_p synctex_node_parent(synctex_node_p node) +{ + return _synctex_tree_parent(node); +} +synctex_node_p synctex_node_parent_sheet(synctex_node_p node) +{ + while(node && synctex_node_type(node) != synctex_node_type_sheet) { + node = _synctex_tree_parent(node); + } + /* exit the while loop either when node is NULL or node is a sheet */ + return node; +} +synctex_node_p synctex_node_parent_form(synctex_node_p node) +{ + while(node && synctex_node_type(node) != synctex_node_type_form) { + node = _synctex_tree_parent(node); + } + /* exit the while loop either when node is NULL or node is a form */ + return node; +} + +/** + * The returned proxy will be the child or a sibling of source. + * The returned proxy has no parent, child nor sibling. + * Used only by __synctex_replace_ref. + * argument to_node: a box, not a proxy nor anything else. + */ +SYNCTEX_INLINE static synctex_node_p __synctex_new_proxy_from_ref_to(synctex_node_p ref, synctex_node_p to_node) { + synctex_node_p proxy = NULL; + if (!ref || !to_node) { + return NULL; + } + switch(synctex_node_type(to_node)) { + case synctex_node_type_vbox: + proxy = _synctex_new_proxy_vbox(ref->class_->scanner); + break; + case synctex_node_type_hbox: + proxy = _synctex_new_proxy_hbox(ref->class_->scanner); + break; + default: + _synctex_error("! __synctex_new_proxy_from_ref_to. Unexpected form child (%s). Please report.", synctex_node_isa(to_node)); + return NULL; + } + if (!proxy) { + _synctex_error("! __synctex_new_proxy_from_ref_to. Internal error. Please report."); + return NULL; + } + _synctex_data_set_h(proxy, _synctex_data_h(ref)); + _synctex_data_set_v(proxy, _synctex_data_v(ref)-_synctex_data_height(to_node)); + _synctex_tree_set_target(proxy,to_node); +# if defined(SYNCTEX_USE_CHARINDEX) + proxy->line_index=to_node?to_node->line_index:0; + proxy->char_index=to_node?to_node->char_index:0; +# endif + return proxy; +} +/** + * The returned proxy will be the child or a sibling of owning_proxy. + * The returned proxy has no parent, nor child. + * Used only by synctex_node_child and synctex_node_sibling + * to create proxies on the fly. + * If the to_node has an already computed sibling, + * then the returned proxy has itself a sibling + * pointing to that already computed sibling. + */ +SYNCTEX_INLINE static synctex_node_p __synctex_new_child_proxy_to(synctex_node_p owner, synctex_node_p to_node) { + synctex_node_p proxy = NULL; + synctex_node_p target = to_node; + if (!owner) { + return NULL; + } + switch(synctex_node_type(target)) { + case synctex_node_type_vbox: + if ((proxy = _synctex_new_proxy_vbox(owner->class_->scanner))) { + exit_standard: + _synctex_data_set_h(proxy, _synctex_data_h(owner)); + _synctex_data_set_v(proxy, _synctex_data_v(owner)); + exit0: + _synctex_tree_set_target(proxy,target); +# if defined(SYNCTEX_USE_CHARINDEX) + proxy->line_index=to_node?to_node->line_index:0; + proxy->char_index=to_node?to_node->char_index:0; +# endif + return proxy; + }; + break; + case synctex_node_type_proxy_vbox: + if ((proxy = _synctex_new_proxy_vbox(owner->class_->scanner))) { + exit_proxy: + target = _synctex_tree_target(to_node); + _synctex_data_set_h(proxy, _synctex_data_h(owner)+_synctex_data_h(to_node)); + _synctex_data_set_v(proxy, _synctex_data_v(owner)+_synctex_data_v(to_node)); + goto exit0; + }; + break; + case synctex_node_type_hbox: + if ((proxy = _synctex_new_proxy_hbox(owner->class_->scanner))) { + goto exit_standard; + }; + break; + case synctex_node_type_proxy_hbox: + if ((proxy = _synctex_new_proxy_hbox(owner->class_->scanner))) { + goto exit_proxy; + }; + break; + case synctex_node_type_proxy: + case synctex_node_type_proxy_last: + if ((proxy = _synctex_new_proxy(owner->class_->scanner))) { + goto exit_proxy; + }; + break; + default: + if ((proxy = _synctex_new_proxy(owner->class_->scanner))) { + goto exit_standard; + }; + break; + } + _synctex_error("! __synctex_new_child_proxy_to. " + "Internal error. " + "Please report."); + return NULL; +} +SYNCTEX_INLINE static synctex_node_p _synctex_tree_set_sibling(synctex_node_p node, synctex_node_p new_sibling); +typedef struct synctex_nns_t { + synctex_node_p first; + synctex_node_p last; + synctex_status_t status; +} synctex_nns_s; +/** + * Given a target node, create a list of proxies. + * The first proxy points to the target node, + * its sibling points to the target's sibling and so on. + * Returns the first created proxy, the last one and + * an error status. + */ +SYNCTEX_INLINE static synctex_nns_s _synctex_new_child_proxies_to(synctex_node_p owner, synctex_node_p to_node) { + synctex_nns_s nns = {NULL,NULL,SYNCTEX_STATUS_OK}; + if ((nns.first = nns.last = __synctex_new_child_proxy_to(owner,to_node))) { + synctex_node_p to_next_sibling = __synctex_tree_sibling(to_node); + synctex_node_p to_sibling; + while ((to_sibling = to_next_sibling)) { + synctex_node_p sibling; + if ((to_next_sibling = __synctex_tree_sibling(to_sibling))) { + /* This is not the last sibling */ + if((sibling = __synctex_new_child_proxy_to(owner,to_sibling))) { + _synctex_tree_set_sibling(nns.last,sibling); + nns.last = sibling; + continue; + } else { + _synctex_error("! _synctex_new_child_proxy_to. " + "Internal error (1). " + "Please report."); + nns.status = SYNCTEX_STATUS_ERROR; + } + } else if((sibling = _synctex_new_proxy_last(owner->class_->scanner))) { + _synctex_tree_set_sibling(nns.last,sibling); + nns.last = sibling; + _synctex_data_set_h(nns.last, _synctex_data_h(nns.first)); + _synctex_data_set_v(nns.last, _synctex_data_v(nns.first)); + _synctex_tree_set_target(nns.last,to_sibling); +# if defined(SYNCTEX_USE_CHARINDEX) + nns.last->line_index=to_sibling->line_index; + nns.last->char_index=to_sibling->char_index; +# endif + } else { + _synctex_error("! _synctex_new_child_proxy_to. " + "Internal error (2). " + "Please report."); + nns.status = SYNCTEX_STATUS_ERROR; + } + break; + } + } + return nns; +} +static char * _synctex_node_abstract(synctex_node_p node); +SYNCTEX_INLINE static synctex_node_p synctex_tree_set_friend(synctex_node_p node,synctex_node_p new_friend) { +#if SYNCTEX_DEBUG + synctex_node_p F = new_friend; + while (F) { + if (node == F) { + printf("THIS IS AN ERROR\n"); + F = new_friend; + while (F) { + printf("%s\n",_synctex_node_abstract(F)); + if (node == F) { + return NULL; + } + F = _synctex_tree_friend(F); + } + return NULL; + } + F = _synctex_tree_friend(F); + } +#endif + return new_friend?_synctex_tree_set_friend(node,new_friend):_synctex_tree_reset_friend(node); +} +/** + * + */ +SYNCTEX_INLINE static synctex_node_p __synctex_node_make_friend(synctex_node_p node, int i) { + synctex_node_p old = NULL; + if (i>=0) { + i = i%(node->class_->scanner->number_of_lists); + old = synctex_tree_set_friend(node,(node->class_->scanner->lists_of_friends)[i]); + (node->class_->scanner->lists_of_friends)[i] = node; +#if SYNCTEX_DEBUG>500 + printf("tl(%i)=>",i); + synctex_node_log(node); + if (synctex_node_parent_form(node)) { + printf("! ERROR. No registration expected!\n"); + } +#endif + } + return old; +} +/** + * All proxies have tlc attributes, on behalf of their target. + * The purpose is to register all af them. + * - argument node: is the proxy, must not be NULL + */ +SYNCTEX_INLINE static synctex_node_p __synctex_proxy_make_friend_and_next_hbox(synctex_node_p node) { + synctex_node_p old = NULL; + synctex_node_p target = _synctex_tree_target(node); + if (target) { + int i = _synctex_data_tag(target)+_synctex_data_line(target); + old = __synctex_node_make_friend(node,i); + } else { + old = __synctex_tree_reset_friend(node); + } + if (synctex_node_type(node) == synctex_node_type_proxy_hbox) { + synctex_node_p sheet = synctex_node_parent_sheet(node); + if (sheet) { + _synctex_tree_set_next_hbox(node,_synctex_tree_next_hbox(sheet)); + _synctex_tree_set_next_hbox(sheet,node); + } + } + return old; +} +/** + * Register a node which have tag, line and column. + * - argument node: the node + */ +SYNCTEX_INLINE static synctex_node_p __synctex_node_make_friend_tlc(synctex_node_p node) { + int i = synctex_node_tag(node)+synctex_node_line(node); + return __synctex_node_make_friend(node,i); +} +/** + * Register a node which have tag, line and column. + * Does nothing if the argument is NULL. + * Calls __synctex_node_make_friend_tlc. + * - argument node: the node + */ +SYNCTEX_INLINE static void _synctex_node_make_friend_tlc(synctex_node_p node) { + if (node) { + __synctex_node_make_friend_tlc(node); + } +} +static synctex_node_p _synctex_node_set_child(synctex_node_p node, synctex_node_p new_child); +/** + * The (first) child of the node, if any, NULL otherwise. + * At parse time, non void box nodes have children. + * All other nodes have no children. + * In order to support pdf forms, proxies are created + * to place form nodes at real locations. + * Ref nodes are replaced by root proxies targeting + * form contents. If root proxies have no children, + * they are created on the fly as proxies to the + * children of the targeted box. + * As such, proxies created here are targeting a + * node that belongs to a form. + * This is the only place where child proxies are created. + */ +synctex_node_p synctex_node_child(synctex_node_p node) { + synctex_node_p child = NULL; + synctex_node_p target = NULL; + if ((child = _synctex_tree_child(node))) { + return child; + } else if ((target = _synctex_tree_target(node))) { + if ((child = synctex_node_child(target))) { + /* This is a proxy with no child + * which target does have a child. */ + synctex_nns_s nns = _synctex_new_child_proxies_to(node, child); + if (nns.first) { + _synctex_node_set_child(node,nns.first); + return nns.first; + } else { + _synctex_error("! synctex_node_child. Internal inconsistency. Please report."); + } + } + } + return NULL; +} +/* + * Set the parent/child bound. + * Things get complicated when new_child has siblings. + * The caller is responsible for releasing the returned value. + */ +static synctex_node_p _synctex_node_set_child(synctex_node_p parent, synctex_node_p new_child) { + if (parent) { + synctex_node_p old = _synctex_tree_set_child(parent,new_child); + synctex_node_p last_child = NULL; + synctex_node_p child; + if ((child = old)) { + do { + _synctex_tree_reset_parent(child); + } while ((child = __synctex_tree_sibling(child))); + } + if ((child = new_child)) { + do { + _synctex_tree_set_parent(child,parent); + last_child = child; + } while ((child = __synctex_tree_sibling(child))); + } + _synctex_tree_set_last(parent,last_child); + return old; + } + return NULL; +} + +/* The last child of the given node, or NULL. + */ +synctex_node_p synctex_node_last_child(synctex_node_p node) { + return _synctex_tree_last(node); +} +/** + * All nodes siblings are properly set up at parse time + * except for non root proxies. + */ +synctex_node_p synctex_node_sibling(synctex_node_p node) { + return node? __synctex_tree_sibling(node): NULL; +} +/** + * All the _synctex_tree_... methods refer to the tree model. + * __synctex_tree_... methods are low level. + */ +/** + * Replace the sibling. + * Connect to the arg_sibling of the new_sibling if relevant. + * - returns the old sibling. + * The caller is responsible for releasing the old sibling. + * The bound to the parent is managed below. + */ +SYNCTEX_INLINE static synctex_node_p _synctex_tree_set_sibling(synctex_node_p node, synctex_node_p new_sibling) { + if (node == new_sibling) { + printf("BOF\n"); + } + synctex_node_p old = node? __synctex_tree_set_sibling(node,new_sibling): NULL; + _synctex_tree_set_arg_sibling(new_sibling,node); + return old; +} +/** + * Replace the sibling. + * Set the parent of the new sibling (and further siblings) + * to the parent of the receiver. + * Also set the last sibling of parent. + * - argument new_sibling: must not be NULL. + * - returns the old sibling. + * The caller is responsible for releasing the old sibling. + */ +static synctex_node_p _synctex_node_set_sibling(synctex_node_p node, synctex_node_p new_sibling) { + if (node && new_sibling) { + synctex_node_p old = _synctex_tree_set_sibling(node,new_sibling); + if (_synctex_tree_has_parent(node)) { + synctex_node_p parent = __synctex_tree_parent(node); + if (parent) { + synctex_node_p N = new_sibling; + while (synctex_YES) { + if (_synctex_tree_has_parent(N)) { + __synctex_tree_set_parent(N,parent); + _synctex_tree_set_last(parent,N); + N = __synctex_tree_sibling(N); + continue; + } else if (N) { + _synctex_error("! synctex_node_sibling. " + "Internal inconsistency. " + "Please report."); + } + break; + } + } + } + return old; + } + return NULL; +} +/** + * The last sibling of the given node, or NULL with node. + */ +synctex_node_p synctex_node_last_sibling(synctex_node_p node) { + synctex_node_p sibling; + do { + sibling = node; + } while((node = synctex_node_sibling(node))); + return sibling; +} +/** + * The next nodes corresponds to a deep first tree traversal. + * Does not create child proxies as side effect contrary to + * the synctex_node_next method above. + * May loop infinitely many times if the tree + * is not properly built (contains loops). + */ +SYNCTEX_INLINE static synctex_node_p _synctex_node_sibling_or_parents(synctex_node_p node) { + while (node) { + synctex_node_p N; + if ((N = __synctex_tree_sibling(node))) { + return N; + } else if ((node = _synctex_tree_parent(node))) { + if (synctex_node_type(node) == synctex_node_type_sheet) {/* EXC_BAD_ACCESS? */ + return NULL; + } else if (synctex_node_type(node) == synctex_node_type_form) { + return NULL; + } + } else { + return NULL; + } + } + return NULL; +} +/** + * The next nodes corresponds to a deep first tree traversal. + * Creates child proxies as side effect. + * May loop infinitely many times if the tree + * is not properly built (contains loops). + */ +synctex_node_p synctex_node_next(synctex_node_p node) { + synctex_node_p N = synctex_node_child(node); + if (N) { + return N; + } + return _synctex_node_sibling_or_parents(node); +} +/** + * The next nodes corresponds to a deep first tree traversal. + * Does not create child proxies as side effect contrary to + * the synctex_node_next method above. + * May loop infinitely many times if the tree + * is not properly built (contains loops). + */ +synctex_node_p _synctex_node_next(synctex_node_p node) { + synctex_node_p N = _synctex_tree_child(node); + if (N) { + return N; + } + return _synctex_node_sibling_or_parents(node); +} +/** + * The node which argument is the sibling. + * - return: NULL if the argument has no parent or + * is the first child of its parent. + * - Input nodes have no arg siblings + */ +synctex_node_p synctex_node_arg_sibling(synctex_node_p node) { +#if 1 + return _synctex_tree_arg_sibling(node); +#else + synctex_node_p N = _synctex_tree_parent(node); + if ((N = _synctex_tree_child(N))) { + do { + synctex_node_p NN = __synctex_tree_sibling(N); + if (NN == node) { + return N; + } + N = NN; + } while (N); + } + return N; +#endif +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark CLASS +# endif + +/* Public node accessor: the type */ +synctex_node_type_t synctex_node_type(synctex_node_p node) { + return node? node->class_->type: synctex_node_type_none; +} + +/* Public node accessor: the type */ +synctex_node_type_t synctex_node_target_type(synctex_node_p node) { + synctex_node_p target = _synctex_tree_target(node); + if (target) { + return (((target)->class_))->type; + } else if (node) { + return (((node)->class_))->type; + } + return synctex_node_type_none; +} + +/* Public node accessor: the human readable type */ +const char * synctex_node_isa(synctex_node_p node) { + static const char * isa[synctex_node_number_of_types] = + {"Not a node", + "input", + "sheet", + "form", + "ref", + "vbox", + "void vbox", + "hbox", + "void hbox", + "kern", + "glue", + "rule", + "math", + "boundary", + "box_bdry", + "proxy", + "last proxy", + "vbox proxy", + "hbox proxy", + "handle"}; + return isa[synctex_node_type(node)]; +} + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark LOG +# endif + +/* Public node logger */ +void synctex_node_log(synctex_node_p node) { + SYNCTEX_MSG_SEND(node,log); +} + +static void _synctex_log_input(synctex_node_p node) { + if (node) { + printf("%s:%i,%s(%i)\n",synctex_node_isa(node), + _synctex_data_tag(node), + _synctex_data_name(node), + _synctex_data_line(node)); + printf("SELF:%p\n",(void *)node); + printf(" SIBLING:%p\n", + (void *)__synctex_tree_sibling(node)); + } +} + +static void _synctex_log_sheet(synctex_node_p node) { + if (node) { + printf("%s:%i",synctex_node_isa(node),_synctex_data_page(node)); + SYNCTEX_PRINT_CHARINDEX_NL; + printf("SELF:%p\n",(void *)node); + printf(" SIBLING:%p\n",(void *)__synctex_tree_sibling(node)); + printf(" PARENT:%p\n",(void *)_synctex_tree_parent(node)); + printf(" CHILD:%p\n",(void *)_synctex_tree_child(node)); + printf(" LEFT:%p\n",(void *)_synctex_tree_friend(node)); + printf(" NEXT_hbox:%p\n",(void *)_synctex_tree_next_hbox(node)); + } +} + +static void _synctex_log_form(synctex_node_p node) { + if (node) { + printf("%s:%i",synctex_node_isa(node),_synctex_data_tag(node)); + SYNCTEX_PRINT_CHARINDEX_NL; + printf("SELF:%p\n",(void *)node); + printf(" SIBLING:%p\n",(void *)__synctex_tree_sibling(node)); + printf(" PARENT:%p\n",(void *)_synctex_tree_parent(node)); + printf(" CHILD:%p\n",(void *)_synctex_tree_child(node)); + printf(" LEFT:%p\n",(void *)_synctex_tree_friend(node)); + } +} + +static void _synctex_log_ref(synctex_node_p node) { + if (node) { + printf("%s:%i:%i,%i", + synctex_node_isa(node), + _synctex_data_tag(node), + _synctex_data_h(node), + _synctex_data_v(node)); + SYNCTEX_PRINT_CHARINDEX_NL; + printf("SELF:%p\n",(void *)node); + printf(" SIBLING:%p\n",(void *)__synctex_tree_sibling(node)); + printf(" PARENT:%p\n",(void *)_synctex_tree_parent(node)); + } +} + +static void _synctex_log_tlchv_node(synctex_node_p node) { + if (node) { + printf("%s:%i,%i,%i:%i,%i", + synctex_node_isa(node), + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_column(node), + _synctex_data_h(node), + _synctex_data_v(node)); + SYNCTEX_PRINT_CHARINDEX_NL; + printf("SELF:%p\n",(void *)node); + printf(" SIBLING:%p\n",(void *)__synctex_tree_sibling(node)); + printf(" PARENT:%p\n",(void *)_synctex_tree_parent(node)); + printf(" CHILD:%p\n",(void *)_synctex_tree_child(node)); + printf(" LEFT:%p\n",(void *)_synctex_tree_friend(node)); + } +} + +static void _synctex_log_kern_node(synctex_node_p node) { + if (node) { + printf("%s:%i,%i,%i:%i,%i:%i", + synctex_node_isa(node), + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_column(node), + _synctex_data_h(node), + _synctex_data_v(node), + _synctex_data_width(node)); + SYNCTEX_PRINT_CHARINDEX_NL; + printf("SELF:%p\n",(void *)node); + printf(" SIBLING:%p\n",(void *)__synctex_tree_sibling(node)); + printf(" PARENT:%p\n",(void *)_synctex_tree_parent(node)); + printf(" CHILD:%p\n",(void *)_synctex_tree_child(node)); + printf(" LEFT:%p\n",(void *)_synctex_tree_friend(node)); + } +} + +static void _synctex_log_rule(synctex_node_p node) { + if (node) { + printf("%s:%i,%i,%i:%i,%i", + synctex_node_isa(node), + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_column(node), + _synctex_data_h(node), + _synctex_data_v(node)); + printf(":%i",_synctex_data_width(node)); + printf(",%i",_synctex_data_height(node)); + printf(",%i",_synctex_data_depth(node)); + SYNCTEX_PRINT_CHARINDEX_NL; + printf("SELF:%p\n",(void *)node); + printf(" SIBLING:%p\n",(void *)__synctex_tree_sibling(node)); + printf(" PARENT:%p\n",(void *)_synctex_tree_parent(node)); + printf(" LEFT:%p\n",(void *)_synctex_tree_friend(node)); + } +} + +static void _synctex_log_void_box(synctex_node_p node) { + if (node) { + printf("%s",synctex_node_isa(node)); + printf(":%i",_synctex_data_tag(node)); + printf(",%i",_synctex_data_line(node)); + printf(",%i",_synctex_data_column(node)); + printf(":%i",_synctex_data_h(node)); + printf(",%i",_synctex_data_v(node)); + printf(":%i",_synctex_data_width(node)); + printf(",%i",_synctex_data_height(node)); + printf(",%i",_synctex_data_depth(node)); + SYNCTEX_PRINT_CHARINDEX_NL; + printf("SELF:%p\n",(void *)node); + printf(" SIBLING:%p\n",(void *)__synctex_tree_sibling(node)); + printf(" PARENT:%p\n",(void *)_synctex_tree_parent(node)); + printf(" CHILD:%p\n",(void *)_synctex_tree_child(node)); + printf(" LEFT:%p\n",(void *)_synctex_tree_friend(node)); + } +} + +static void _synctex_log_vbox(synctex_node_p node) { + if (node) { + printf("%s",synctex_node_isa(node)); + printf(":%i",_synctex_data_tag(node)); + printf(",%i",_synctex_data_line(node)); + printf(",%i",_synctex_data_column(node)); + printf(":%i",_synctex_data_h(node)); + printf(",%i",_synctex_data_v(node)); + printf(":%i",_synctex_data_width(node)); + printf(",%i",_synctex_data_height(node)); + printf(",%i",_synctex_data_depth(node)); + SYNCTEX_PRINT_CHARINDEX_NL; + printf("SELF:%p\n",(void *)node); + printf(" SIBLING:%p\n",(void *)__synctex_tree_sibling(node)); + printf(" PARENT:%p\n",(void *)_synctex_tree_parent(node)); + printf(" CHILD:%p\n",(void *)_synctex_tree_child(node)); + printf(" LEFT:%p\n",(void *)_synctex_tree_friend(node)); + printf(" NEXT_hbox:%p\n",(void *)_synctex_tree_next_hbox(node)); + } +} + +static void _synctex_log_hbox(synctex_node_p node) { + if (node) { + printf("%s",synctex_node_isa(node)); + printf(":%i",_synctex_data_tag(node)); + printf(",%i~%i*%i",_synctex_data_line(node),_synctex_data_mean_line(node),_synctex_data_weight(node)); + printf(",%i",_synctex_data_column(node)); + printf(":%i",_synctex_data_h(node)); + printf(",%i",_synctex_data_v(node)); + printf(":%i",_synctex_data_width(node)); + printf(",%i",_synctex_data_height(node)); + printf(",%i",_synctex_data_depth(node)); + printf("/%i",_synctex_data_h_V(node)); + printf(",%i",_synctex_data_v_V(node)); + printf(":%i",_synctex_data_width_V(node)); + printf(",%i",_synctex_data_height_V(node)); + printf(",%i",_synctex_data_depth_V(node)); + SYNCTEX_PRINT_CHARINDEX_NL; + printf("SELF:%p\n",(void *)node); + printf(" SIBLING:%p\n",(void *)__synctex_tree_sibling(node)); + printf(" PARENT:%p\n",(void *)_synctex_tree_parent(node)); + printf(" CHILD:%p\n",(void *)_synctex_tree_child(node)); + printf(" LEFT:%p\n",(void *)_synctex_tree_friend(node)); + printf(" NEXT_hbox:%p\n",(void *)_synctex_tree_next_hbox(node)); + } +} +static void _synctex_log_proxy(synctex_node_p node) { + if (node) { + synctex_node_p N = _synctex_tree_target(node); + printf("%s",synctex_node_isa(node)); + printf(":%i",_synctex_data_h(node)); + printf(",%i",_synctex_data_v(node)); + SYNCTEX_PRINT_CHARINDEX_NL; + printf("SELF:%p\n",(void *)node); + printf(" SIBLING:%p\n",(void *)__synctex_tree_sibling(node)); + printf(" LEFT:%p\n",(void *)_synctex_tree_friend(node)); + printf(" ->%s\n",_synctex_node_abstract(N)); + } +} +static void _synctex_log_handle(synctex_node_p node) { + if (node) { + synctex_node_p N = _synctex_tree_target(node); + printf("%s",synctex_node_isa(node)); + SYNCTEX_PRINT_CHARINDEX_NL; + printf("SELF:%p\n",(void *)node); + printf(" SIBLING:%p\n",(void *)__synctex_tree_sibling(node)); + printf(" ->%s\n",_synctex_node_abstract(N)); + } +} + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark SYNCTEX_DISPLAY +# endif + +int synctex_scanner_display_switcher(synctex_scanner_p scanR) { + return scanR->display_switcher; +} +void synctex_scanner_set_display_switcher(synctex_scanner_p scanR, int switcher) { + scanR->display_switcher = switcher; +} +static const char * const _synctex_display_prompt = "................................"; + +static char * _synctex_scanner_display_prompt_down(synctex_scanner_p scanR) { + if (scanR->display_prompt>_synctex_display_prompt) { + --scanR->display_prompt; + } + return scanR->display_prompt; +} +static char * _synctex_scanner_display_prompt_up(synctex_scanner_p scanR) { + if (scanR->display_prompt+1<_synctex_display_prompt+strlen(_synctex_display_prompt)) { + ++scanR->display_prompt; + } + return scanR->display_prompt; +} + +void synctex_node_display(synctex_node_p node) { + if (node) { + synctex_scanner_p scanR = node->class_->scanner; + if (scanR) { + if (scanR->display_switcher<0) { + SYNCTEX_MSG_SEND(node, display); + } else if (scanR->display_switcher>0 && --scanR->display_switcher>0) { + SYNCTEX_MSG_SEND(node, display); + } else if (scanR->display_switcher-->=0) { + printf("%s Next display skipped. Reset display switcher.\n",node->class_->scanner->display_prompt); + } + } else { + SYNCTEX_MSG_SEND(node, display); + } + } +} +static char * _synctex_node_abstract(synctex_node_p node) { + SYNCTEX_PARAMETER_ASSERT(node || node->class_); + return (node && node->class_->abstract)? node->class_->abstract(node):"none"; +} + +SYNCTEX_INLINE static void _synctex_display_child(synctex_node_p node) { + synctex_node_p N = _synctex_tree_child(node); + if (N) { + _synctex_scanner_display_prompt_down(N->class_->scanner); + synctex_node_display(N); + _synctex_scanner_display_prompt_up(N->class_->scanner); + } +} + +SYNCTEX_INLINE static void _synctex_display_sibling(synctex_node_p node) { + synctex_node_display(__synctex_tree_sibling(node)); +} +#define SYNCTEX_ABSTRACT_MAX 128 +static char * _synctex_abstract_input(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + snprintf(abstract,SYNCTEX_ABSTRACT_MAX,"Input:%i:%s(%i)" SYNCTEX_PRINT_CHARINDEX_FMT, + _synctex_data_tag(node), + _synctex_data_name(node), + _synctex_data_line(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + } + return abstract; +} + +static void _synctex_display_input(synctex_node_p node) { + if (node) { + printf("Input:%i:%s(%i)" + SYNCTEX_PRINT_CHARINDEX_FMT + "\n", + _synctex_data_tag(node), + _synctex_data_name(node), + _synctex_data_line(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + synctex_node_display(__synctex_tree_sibling(node)); + } +} + +static char * _synctex_abstract_sheet(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + snprintf(abstract,SYNCTEX_ABSTRACT_MAX,"{%i...}" SYNCTEX_PRINT_CHARINDEX_FMT, + _synctex_data_page(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + } + return abstract; +} + +static void _synctex_display_sheet(synctex_node_p node) { + if (node) { + printf("%s{%i" + SYNCTEX_PRINT_CHARINDEX_FMT + "\n", + node->class_->scanner->display_prompt, + _synctex_data_page(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + _synctex_display_child(node); + printf("%s}\n",node->class_->scanner->display_prompt); + _synctex_display_sibling(node); + } +} + +static char * _synctex_abstract_form(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + snprintf(abstract,SYNCTEX_ABSTRACT_MAX,"<%i...>" SYNCTEX_PRINT_CHARINDEX_FMT, + _synctex_data_tag(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + SYNCTEX_PRINT_CHARINDEX; + } + return abstract; +} + +static void _synctex_display_form(synctex_node_p node) { + if (node) { + printf("%s<%i" + SYNCTEX_PRINT_CHARINDEX_FMT + "\n", + node->class_->scanner->display_prompt, + _synctex_data_tag(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + _synctex_display_child(node); + printf("%s>\n",node->class_->scanner->display_prompt); + _synctex_display_sibling(node); + } +} + +static char * _synctex_abstract_vbox(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + snprintf(abstract,SYNCTEX_ABSTRACT_MAX,"[%i,%i:%i,%i:%i,%i,%i...]" + SYNCTEX_PRINT_CHARINDEX_FMT, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node), + _synctex_data_width(node), + _synctex_data_height(node), + _synctex_data_depth(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + } + return abstract; +} + +static void _synctex_display_vbox(synctex_node_p node) { + if (node) { + printf("%s[%i,%i:%i,%i:%i,%i,%i" + SYNCTEX_PRINT_CHARINDEX_FMT + "\n", + node->class_->scanner->display_prompt, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node), + _synctex_data_width(node), + _synctex_data_height(node), + _synctex_data_depth(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + _synctex_display_child(node); + printf("%s]\n%slast:%s\n", + node->class_->scanner->display_prompt, + node->class_->scanner->display_prompt, + _synctex_node_abstract(_synctex_tree_last(node))); + _synctex_display_sibling(node); + } +} + +static char * _synctex_abstract_hbox(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + snprintf(abstract,SYNCTEX_ABSTRACT_MAX,"(%i,%i~%i*%i:%i,%i:%i,%i,%i...)" + SYNCTEX_PRINT_CHARINDEX_FMT, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_mean_line(node), + _synctex_data_weight(node), + _synctex_data_h(node), + _synctex_data_v(node), + _synctex_data_width(node), + _synctex_data_height(node), + _synctex_data_depth(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + } + return abstract; +} + +static void _synctex_display_hbox(synctex_node_p node) { + if (node) { + printf("%s(%i,%i~%i*%i:%i,%i:%i,%i,%i" + SYNCTEX_PRINT_CHARINDEX_FMT + "\n", + node->class_->scanner->display_prompt, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_mean_line(node), + _synctex_data_weight(node), + _synctex_data_h(node), + _synctex_data_v(node), + _synctex_data_width(node), + _synctex_data_height(node), + _synctex_data_depth(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + _synctex_display_child(node); + printf("%s)\n%slast:%s\n", + node->class_->scanner->display_prompt, + node->class_->scanner->display_prompt, + _synctex_node_abstract(_synctex_tree_last(node))); + _synctex_display_sibling(node); + } +} + +static char * _synctex_abstract_void_vbox(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + snprintf(abstract,SYNCTEX_ABSTRACT_MAX,"v%i,%i;%i,%i:%i,%i,%i" + SYNCTEX_PRINT_CHARINDEX_FMT + "\n", + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node), + _synctex_data_width(node), + _synctex_data_height(node), + _synctex_data_depth(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + } + return abstract; +} + +static void _synctex_display_void_vbox(synctex_node_p node) { + if (node) { + printf("%sv%i,%i;%i,%i:%i,%i,%i" + SYNCTEX_PRINT_CHARINDEX_FMT + "\n", + node->class_->scanner->display_prompt, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node), + _synctex_data_width(node), + _synctex_data_height(node), + _synctex_data_depth(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + _synctex_display_sibling(node); + } +} + +static char * _synctex_abstract_void_hbox(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + snprintf(abstract,SYNCTEX_ABSTRACT_MAX,"h%i,%i:%i,%i:%i,%i,%i" + SYNCTEX_PRINT_CHARINDEX_FMT, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node), + _synctex_data_width(node), + _synctex_data_height(node), + _synctex_data_depth(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + } + return abstract; +} + +static void _synctex_display_void_hbox(synctex_node_p node) { + if (node) { + printf("%sh%i,%i:%i,%i:%i,%i,%i" + SYNCTEX_PRINT_CHARINDEX_FMT + "\n", + node->class_->scanner->display_prompt, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node), + _synctex_data_width(node), + _synctex_data_height(node), + _synctex_data_depth(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + _synctex_display_sibling(node); + } +} + +static char * _synctex_abstract_glue(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + snprintf(abstract,SYNCTEX_ABSTRACT_MAX,"glue:%i,%i:%i,%i" + SYNCTEX_PRINT_CHARINDEX_FMT, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + } + return abstract; +} + +static void _synctex_display_glue(synctex_node_p node) { + if (node) { + printf("%sglue:%i,%i:%i,%i" + SYNCTEX_PRINT_CHARINDEX_FMT + "\n", + node->class_->scanner->display_prompt, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + _synctex_display_sibling(node); + } +} + +static char * _synctex_abstract_rule(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + snprintf(abstract,SYNCTEX_ABSTRACT_MAX,"rule:%i,%i:%i,%i:%i,%i,%i" + SYNCTEX_PRINT_CHARINDEX_FMT, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node), + _synctex_data_width(node), + _synctex_data_height(node), + _synctex_data_depth(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + } + return abstract; +} + +static void _synctex_display_rule(synctex_node_p node) { + if (node) { + printf("%srule:%i,%i:%i,%i:%i,%i,%i" + SYNCTEX_PRINT_CHARINDEX_FMT + "\n", + node->class_->scanner->display_prompt, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node), + _synctex_data_width(node), + _synctex_data_height(node), + _synctex_data_depth(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + _synctex_display_sibling(node); + } +} + +static char * _synctex_abstract_math(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + snprintf(abstract,SYNCTEX_ABSTRACT_MAX,"math:%i,%i:%i,%i" + SYNCTEX_PRINT_CHARINDEX_FMT, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + } + return abstract; +} + +static void _synctex_display_math(synctex_node_p node) { + if (node) { + printf("%smath:%i,%i:%i,%i" + SYNCTEX_PRINT_CHARINDEX_FMT + "\n", + node->class_->scanner->display_prompt, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + _synctex_display_sibling(node); + } +} + +static char * _synctex_abstract_kern(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + snprintf(abstract,SYNCTEX_ABSTRACT_MAX,"kern:%i,%i:%i,%i:%i" + SYNCTEX_PRINT_CHARINDEX_FMT, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node), + _synctex_data_width(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + } + return abstract; +} + +static void _synctex_display_kern(synctex_node_p node) { + if (node) { + printf("%skern:%i,%i:%i,%i:%i" + SYNCTEX_PRINT_CHARINDEX_FMT + "\n", + node->class_->scanner->display_prompt, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node), + _synctex_data_width(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + _synctex_display_sibling(node); + } +} + +static char * _synctex_abstract_boundary(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + snprintf(abstract,SYNCTEX_ABSTRACT_MAX,"boundary:%i,%i:%i,%i" + SYNCTEX_PRINT_CHARINDEX_FMT, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + } + return abstract; +} + +static void _synctex_display_boundary(synctex_node_p node) { + if (node) { + printf("%sboundary:%i,%i:%i,%i" + SYNCTEX_PRINT_CHARINDEX_FMT + "\n", + node->class_->scanner->display_prompt, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + _synctex_display_sibling(node); + } +} + +static char * _synctex_abstract_box_bdry(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + snprintf(abstract,SYNCTEX_ABSTRACT_MAX,"box bdry:%i,%i:%i,%i" SYNCTEX_PRINT_CHARINDEX_FMT, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + } + return abstract; +} + +static void _synctex_display_box_bdry(synctex_node_p node) { + if (node) { + printf("%sbox bdry:%i,%i:%i,%i", + node->class_->scanner->display_prompt, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node)); + SYNCTEX_PRINT_CHARINDEX_NL; + _synctex_display_sibling(node); + } +} + +static char * _synctex_abstract_ref(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + snprintf(abstract,SYNCTEX_ABSTRACT_MAX,"form ref:%i:%i,%i" SYNCTEX_PRINT_CHARINDEX_FMT, + _synctex_data_tag(node), + _synctex_data_h(node), + _synctex_data_v(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + } + return abstract; +} + +static void _synctex_display_ref(synctex_node_p node) { + if (node) { + printf("%sform ref:%i:%i,%i", + node->class_->scanner->display_prompt, + _synctex_data_tag(node), + _synctex_data_h(node), + _synctex_data_v(node)); + SYNCTEX_PRINT_CHARINDEX_NL; + _synctex_display_sibling(node); + } +} +static char * _synctex_abstract_proxy(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + synctex_node_p N = _synctex_tree_target(node); + snprintf(abstract,SYNCTEX_ABSTRACT_MAX,"%s:%i,%i:%i,%i/%p%s", + synctex_node_isa(node), + synctex_node_tag(node), + synctex_node_line(node), + _synctex_data_h(node), + _synctex_data_v(node), + node, + _synctex_node_abstract(N)); + } + return abstract; +} +static void _synctex_display_proxy(synctex_node_p node) { + if (node) { + synctex_node_p N = _synctex_tree_target(node); + printf("%s%s:%i,%i:%i,%i", + node->class_->scanner->display_prompt, + synctex_node_isa(node), + synctex_node_tag(node), + synctex_node_line(node), + _synctex_data_h(node), + _synctex_data_v(node)); + if (N) { + printf("=%i,%i:%i,%i,%i->%s", + synctex_node_h(node), + synctex_node_v(node), + synctex_node_width(node), + synctex_node_height(node), + synctex_node_depth(node), + _synctex_node_abstract(N)); + } + printf("\n"); + _synctex_display_child(node); + _synctex_display_sibling(node); + } +} +static char * _synctex_abstract_proxy_vbox(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + snprintf(abstract,SYNCTEX_ABSTRACT_MAX, + "[*%i,%i:%i,%i:%i,%i,%i...*]" + SYNCTEX_PRINT_CHARINDEX_FMT, + synctex_node_tag(node), + synctex_node_line(node), + synctex_node_h(node), + synctex_node_v(node), + synctex_node_width(node), + synctex_node_height(node), + synctex_node_depth(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + } + return abstract; +} + +static void _synctex_display_proxy_vbox(synctex_node_p node) { + if (node) { + printf("%s[*%i,%i:%i,%i:%i,%i,%i" + SYNCTEX_PRINT_CHARINDEX_FMT + "\n", + node->class_->scanner->display_prompt, + synctex_node_tag(node), + synctex_node_line(node), + synctex_node_h(node), + synctex_node_v(node), + synctex_node_width(node), + synctex_node_height(node), + synctex_node_depth(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + _synctex_display_child(node); + printf("%s*]\n%slast:%s\n", + node->class_->scanner->display_prompt, + node->class_->scanner->display_prompt, + _synctex_node_abstract(_synctex_tree_last(node))); + _synctex_display_sibling(node); + } +} + +static char * _synctex_abstract_proxy_hbox(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + snprintf(abstract,SYNCTEX_ABSTRACT_MAX,"(*%i,%i~%i*%i:%i,%i:%i,%i,%i...*)/%p" + SYNCTEX_PRINT_CHARINDEX_FMT, + synctex_node_tag(node), + synctex_node_line(node), + synctex_node_mean_line(node), + synctex_node_weight(node), + synctex_node_h(node), + synctex_node_v(node), + synctex_node_width(node), + synctex_node_height(node), + synctex_node_depth(node), + node + SYNCTEX_PRINT_CHARINDEX_WHAT); + } + return abstract; +} + +static void _synctex_display_proxy_hbox(synctex_node_p node) { + if (node) { + printf("%s(*%i,%i~%i*%i:%i,%i:%i,%i,%i" + SYNCTEX_PRINT_CHARINDEX_FMT + "\n", + node->class_->scanner->display_prompt, + synctex_node_tag(node), + synctex_node_line(node), + synctex_node_mean_line(node), + synctex_node_weight(node), + synctex_node_h(node), + synctex_node_v(node), + synctex_node_width(node), + synctex_node_height(node), + synctex_node_depth(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + _synctex_display_child(node); + printf("%s*)\n%slast:%s\n", + node->class_->scanner->display_prompt, + node->class_->scanner->display_prompt, + _synctex_node_abstract(_synctex_tree_last(node))); + _synctex_display_sibling(node); + } +} + +static char * _synctex_abstract_handle(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + synctex_node_p N = _synctex_tree_target(node); + if (N && !N->class_) { + exit(1); + } + snprintf(abstract,SYNCTEX_ABSTRACT_MAX,"%s:%s", + synctex_node_isa(node), + (N?_synctex_node_abstract(N):"")); + } + return abstract; +} +static void _synctex_display_handle(synctex_node_p node) { + if (node) { + synctex_node_p N = _synctex_tree_target(node); + printf("%s%s(%i):->%s\n", + node->class_->scanner->display_prompt, + synctex_node_isa(node), + _synctex_data_weight(N), + _synctex_node_abstract(N)); + _synctex_display_child(node); + _synctex_display_sibling(node); + } +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark STATUS +# endif + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Prototypes +# endif +typedef struct { + size_t size; + synctex_status_t status; +} synctex_zs_s; +static synctex_zs_s _synctex_buffer_get_available_size(synctex_scanner_p scanner, size_t size); +static synctex_status_t _synctex_next_line(synctex_scanner_p scanner); +static synctex_status_t _synctex_match_string(synctex_scanner_p scanner, const char * the_string); + +typedef struct synctex_ns_t { + synctex_node_p node; + synctex_status_t status; +} synctex_ns_s; +static synctex_ns_s __synctex_parse_new_input(synctex_scanner_p scanner); +static synctex_status_t _synctex_scan_preamble(synctex_scanner_p scanner); +typedef struct { + float value; + synctex_status_t status; +} synctex_fs_s; +static synctex_fs_s _synctex_scan_float_and_dimension(synctex_scanner_p scanner); +static synctex_status_t _synctex_scan_post_scriptum(synctex_scanner_p scanner); +static synctex_status_t _synctex_scan_postamble(synctex_scanner_p scanner); +static synctex_status_t _synctex_setup_visible_hbox(synctex_node_p box); +static synctex_status_t _synctex_scan_content(synctex_scanner_p scanner); +int synctex_scanner_pre_x_offset(synctex_scanner_p scanner); +int synctex_scanner_pre_y_offset(synctex_scanner_p scanner); +const char * synctex_scanner_get_output_fmt(synctex_scanner_p scanner); + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark SCANNER UTILITIES +# endif + +# define SYNCTEX_FILE (scanner->reader->file) + +/** + * Try to ensure that the buffer contains at least size bytes. + * Passing a huge size argument means the whole buffer length. + * Passing a 0 size argument means return the available buffer length, without reading the file. + * In that case, the return status is always SYNCTEX_STATUS_OK unless the given scanner is NULL. + * The size_t value returned is the number of bytes now available in the buffer. This is a nonnegative integer, it may take the value 0. + * It is the responsibility of the caller to test whether this size is conforming to its needs. + * Negative values may return in case of error, actually + * when there was an error reading the synctex file. + * - parameter scanner: The owning scanner. When NULL, returns SYNCTEX_STATUS_BAD_ARGUMENT. + * - parameter expected: expected number of bytes. + * - returns: a size and a status. + */ +static synctex_zs_s _synctex_buffer_get_available_size(synctex_scanner_p scanner, size_t expected) { + size_t size = 0; + if (NULL == scanner) { + return (synctex_zs_s){0,SYNCTEX_STATUS_BAD_ARGUMENT}; + } + if (expected>scanner->reader->size){ + expected = scanner->reader->size; + } + size = SYNCTEX_END - SYNCTEX_CUR; /* available is the number of unparsed chars in the buffer */ + if (expected<=size) { + /* There are already sufficiently many characters in the buffer */ + return (synctex_zs_s){size,SYNCTEX_STATUS_OK}; + } + if (SYNCTEX_FILE) { + /* Copy the remaining part of the buffer to the beginning, + * then read the next part of the file */ + int already_read = 0; +# if defined(SYNCTEX_USE_CHARINDEX) + scanner->reader->charindex_offset += SYNCTEX_CUR - SYNCTEX_START; +# endif + if (size) { + memmove(SYNCTEX_START, SYNCTEX_CUR, size); + } + SYNCTEX_CUR = SYNCTEX_START + size; /* the next character after the move, will change. */ + /* Fill the buffer up to its end */ + already_read = gzread(SYNCTEX_FILE,(void *)SYNCTEX_CUR,(int)(SYNCTEX_BUFFER_SIZE - size)); + if (already_read>0) { + /* We assume that 0<already_read<=SYNCTEX_BUFFER_SIZE - size, such that + * SYNCTEX_CUR + already_read = SYNCTEX_START + size + already_read <= SYNCTEX_START + SYNCTEX_BUFFER_SIZE */ + SYNCTEX_END = SYNCTEX_CUR + already_read; + /* If the end of the file was reached, all the required SYNCTEX_BUFFER_SIZE - available + * may not be filled with values from the file. + * In that case, the buffer should stop properly after already_read characters. */ + * SYNCTEX_END = '\0'; /* there is enough room */ + SYNCTEX_CUR = SYNCTEX_START; + /* May be available is less than size, the caller will have to test. */ + return (synctex_zs_s){SYNCTEX_END - SYNCTEX_CUR,SYNCTEX_STATUS_OK}; + } else if (0>already_read) { + /* There is a possible error in reading the file */ + int errnum = 0; + const char * error_string = gzerror(SYNCTEX_FILE, &errnum); + if (Z_ERRNO == errnum) { + /* There is an error in zlib caused by the file system */ + _synctex_error("gzread error from the file system (%i)",errno); + return (synctex_zs_s){0,SYNCTEX_STATUS_ERROR}; + } else if (errnum) { + _synctex_error("gzread error (%i:%i,%s)",already_read,errnum,error_string); + return (synctex_zs_s){0,SYNCTEX_STATUS_ERROR}; + } + } + /* Nothing was read, we are at the end of the file. */ + gzclose(SYNCTEX_FILE); + SYNCTEX_FILE = NULL; + SYNCTEX_END = SYNCTEX_CUR; + SYNCTEX_CUR = SYNCTEX_START; + * SYNCTEX_END = '\0';/* Terminate the string properly.*/ + /* there might be a bit of text left */ + return (synctex_zs_s){SYNCTEX_END - SYNCTEX_CUR,SYNCTEX_STATUS_EOF}; + } + /* We cannot enlarge the buffer because the end of the file was reached. */ + return (synctex_zs_s){size,SYNCTEX_STATUS_EOF}; +} + +/* Used when parsing the synctex file. + * Advance to the next character starting a line. + * Actually, only '\n' is recognized as end of line marker. + * On normal completion, the returned value is the number of unparsed characters available in the buffer. + * In general, it is a positive value, 0 meaning that the end of file was reached. + * -1 is returned in case of error, actually because there was an error while feeding the buffer. + * When the function returns with no error, SYNCTEX_CUR points to the first character of the next line, if any. + * J. Laurens: Sat May 10 07:52:31 UTC 2008 + */ +static synctex_status_t _synctex_next_line(synctex_scanner_p scanner) { + synctex_status_t status = SYNCTEX_STATUS_OK; + if (NULL == scanner) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } +infinite_loop: + while(SYNCTEX_CUR<SYNCTEX_END) { + if (*SYNCTEX_CUR == '\n') { + ++SYNCTEX_CUR; + ++scanner->reader->line_number; + return _synctex_buffer_get_available_size(scanner, 1).status; + } + ++SYNCTEX_CUR; + } + /* Here, we have SYNCTEX_CUR == SYNCTEX_END, such that the next call to _synctex_buffer_get_available_size + * will read another bunch of synctex file. Little by little, we advance to the end of the file. */ + status = _synctex_buffer_get_available_size(scanner, 1).status; + if (status<=SYNCTEX_STATUS_EOF) { + return status; + } + goto infinite_loop; +} + +/* Scan the given string. + * Both scanner and the_string must not be NULL, and the_string must not be 0 length. + * SYNCTEX_STATUS_OK is returned if the string is found, + * SYNCTEX_STATUS_EOF is returned when the EOF is reached, + * SYNCTEX_STATUS_NOT_OK is returned is the string is not found, + * an error status is returned otherwise. + * This is a critical method because buffering renders things more difficult. + * The given string might be as long as the maximum size_t value. + * As side effect, the buffer state may have changed if the given argument string can't fit into the buffer. + */ +static synctex_status_t _synctex_match_string(synctex_scanner_p scanner, const char * the_string) { + size_t tested_len = 0; /* the number of characters at the beginning of the_string that match */ + size_t remaining_len = 0; /* the number of remaining characters of the_string that should match */ + size_t available = 0; + synctex_zs_s zs = {0,0}; + if (NULL == scanner || NULL == the_string) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + remaining_len = strlen(the_string); /* All the_string should match */ + if (0 == remaining_len) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + /* How many characters available in the buffer? */ + zs = _synctex_buffer_get_available_size(scanner,remaining_len); + if (zs.status<SYNCTEX_STATUS_EOF) { + return zs.status; + } + /* Maybe we have less characters than expected because the buffer is too small. */ + if (zs.size>=remaining_len) { + /* The buffer is sufficiently big to hold the expected number of characters. */ + if (strncmp((char *)SYNCTEX_CUR,the_string,remaining_len)) { + return SYNCTEX_STATUS_NOT_OK; + } + return_OK: + /* Advance SYNCTEX_CUR to the next character after the_string. */ + SYNCTEX_CUR += remaining_len; + return SYNCTEX_STATUS_OK; + } else if (strncmp((char *)SYNCTEX_CUR,the_string,zs.size)) { + /* No need to go further, this is not the expected string in the buffer. */ + return SYNCTEX_STATUS_NOT_OK; + } else if (SYNCTEX_FILE) { + /* The buffer was too small to contain remaining_len characters. + * We have to cut the string into pieces. */ + z_off_t offset = 0L; + /* the first part of the string is found, advance the_string to the next untested character. */ + the_string += zs.size; + /* update the remaining length and the parsed length. */ + remaining_len -= zs.size; + tested_len += zs.size; + SYNCTEX_CUR += zs.size; /* We validate the tested characters. */ + if (0 == remaining_len) { + /* Nothing left to test, we have found the given string. */ + return SYNCTEX_STATUS_OK; + } + /* We also have to record the current state of the file cursor because + * if the_string does not match, all this should be a totally blank operation, + * for which the file and buffer states should not be modified at all. + * In fact, the states of the buffer before and after this function are in general different + * but they are totally equivalent as long as the values of the buffer before SYNCTEX_CUR + * can be safely discarded. */ + offset = gztell(SYNCTEX_FILE); + /* offset now corresponds to the first character of the file that was not buffered. */ + /* SYNCTEX_CUR - SYNCTEX_START is the number of chars that where already buffered and + * that match the head of the_string. If in fine the_string does not match, all these chars must be recovered + * because the whole buffer contents is replaced in _synctex_buffer_get_available_size. + * They were buffered from offset-len location in the file. */ + offset -= SYNCTEX_CUR - SYNCTEX_START; + more_characters: + /* There is still some work to be done, so read another bunch of file. + * This is the second call to _synctex_buffer_get_available_size, + * which means that the actual contents of the buffer will be discarded. + * We will definitely have to recover the previous state in case we do not find the expected string. */ + zs = _synctex_buffer_get_available_size(scanner,remaining_len); + if (zs.status<SYNCTEX_STATUS_EOF) { + return zs.status; /* This is an error, no need to go further. */ + } + if (zs.size==0) { + /* Missing characters: recover the initial state of the file and return. */ + return_NOT_OK: + if (offset != gzseek(SYNCTEX_FILE,offset,SEEK_SET)) { + /* This is a critical error, we could not recover the previous state. */ + _synctex_error("Can't seek file"); + return SYNCTEX_STATUS_ERROR; + } + /* Next time we are asked to fill the buffer, + * we will read a complete bunch of text from the file. */ + SYNCTEX_CUR = SYNCTEX_END; + return SYNCTEX_STATUS_NOT_OK; + } + if (zs.size<remaining_len) { + /* We'll have to loop one more time. */ + if (strncmp((char *)SYNCTEX_CUR,the_string,zs.size)) { + /* This is not the expected string, recover the previous state and return. */ + goto return_NOT_OK; + } + /* Advance the_string to the first untested character. */ + the_string += available; + /* update the remaining length and the parsed length. */ + remaining_len -= zs.size; + tested_len += zs.size; + SYNCTEX_CUR += zs.size; /* We validate the tested characters. */ + goto more_characters; + } + /* This is the last step. */ + if (strncmp((char *)SYNCTEX_CUR,the_string,remaining_len)) { + /* This is not the expected string, recover the previous state and return. */ + goto return_NOT_OK; + } + goto return_OK; + } else { + /* The buffer can't contain the given string argument, and the EOF was reached */ + return SYNCTEX_STATUS_EOF; + } +} + +/* Used when parsing the synctex file. + * Decode an integer. + * First, field separators, namely ':' and ',' characters are skipped + * The returned value is negative if there is an unrecoverable error. + * It is SYNCTEX_STATUS_NOT_OK if an integer could not be parsed, for example + * if the characters at the current cursor position are not digits or + * if the end of the file has been reached. + * It is SYNCTEX_STATUS_OK if an int has been successfully parsed. + * The given scanner argument must not be NULL, on the contrary, value_ref may be NULL. + */ +static synctex_is_s _synctex_decode_int(synctex_scanner_p scanner) { + char * ptr = NULL; + char * end = NULL; + synctex_zs_s zs = {0,0}; + int result; + if (NULL == scanner) { + return (synctex_is_s){0, SYNCTEX_STATUS_BAD_ARGUMENT}; + } + zs = _synctex_buffer_get_available_size(scanner, SYNCTEX_BUFFER_MIN_SIZE); + if (zs.status<SYNCTEX_STATUS_EOF) { + return (synctex_is_s){0,zs.status}; + } + if (zs.size==0) { + return (synctex_is_s){0,SYNCTEX_STATUS_NOT_OK}; + } + ptr = SYNCTEX_CUR; + /* Optionally parse the separator */ + if (*ptr==':' || *ptr==',') { + ++ptr; + --zs.size; + if (zs.size==0) { + return (synctex_is_s){0,SYNCTEX_STATUS_NOT_OK}; + } + } + result = (int)strtol(ptr, &end, 10); + if (end>ptr) { + SYNCTEX_CUR = end; + return (synctex_is_s){result,SYNCTEX_STATUS_OK}; + } + return (synctex_is_s){result,SYNCTEX_STATUS_NOT_OK}; +} +static synctex_is_s _synctex_decode_int_opt(synctex_scanner_p scanner, int default_value) { + char * ptr = NULL; + char * end = NULL; + synctex_zs_s zs = {0, 0}; + if (NULL == scanner) { + return (synctex_is_s){default_value, SYNCTEX_STATUS_BAD_ARGUMENT}; + } + zs = _synctex_buffer_get_available_size(scanner, SYNCTEX_BUFFER_MIN_SIZE); + if (zs.status<SYNCTEX_STATUS_EOF) { + return (synctex_is_s){default_value,zs.status}; + } + if (zs.size==0) { + return (synctex_is_s){default_value,SYNCTEX_STATUS_OK}; + } + ptr = SYNCTEX_CUR; + /* Comma separator required */ + if (*ptr==',') { + int result; + ++ptr; + --zs.size; + if (zs.size==0) { + return (synctex_is_s){default_value,SYNCTEX_STATUS_NOT_OK}; + } + result = (int)strtol(ptr, &end, 10); + if (end>ptr) { + SYNCTEX_CUR = end; + return (synctex_is_s){result,SYNCTEX_STATUS_OK}; + } + return (synctex_is_s){default_value,SYNCTEX_STATUS_NOT_OK}; + } + return (synctex_is_s){default_value,SYNCTEX_STATUS_OK}; +} +/* Used when parsing the synctex file. + * Decode an integer for a v field. + * Try the _synctex_decode_int version and set the last v field scanned. + * If it does not succeed, tries to match an '=' sign, + * which is a shortcut for the last v field scanned. + */ +# define SYNCTEX_INPUT_COMEQUALS ",=" +static synctex_is_s _synctex_decode_int_v(synctex_scanner_p scanner) { + synctex_is_s is = _synctex_decode_int(scanner); + if (SYNCTEX_STATUS_OK == is.status) { + scanner->reader->lastv = is.integer; + return is; + } + is.status = _synctex_match_string(scanner,SYNCTEX_INPUT_COMEQUALS); + if (is.status<SYNCTEX_STATUS_OK) { + return is; + } + is.integer = scanner->reader->lastv; + return is; +} + +/* The purpose of this function is to read a string. + * A string is an array of characters from the current parser location + * and before the next '\n' character. + * If a string was properly decoded, it is returned in value_ref and + * the cursor points to the new line marker. + * The returned string was allocated on the heap, the caller is the owner and + * is responsible to free it in due time, + * unless it transfers the ownership to another object. + * If no string is parsed, * value_ref is undefined. + * The maximum length of a string that a scanner can decode is platform dependent, namely UINT_MAX. + * If you just want to blindly parse the file up to the end of the current line, + * use _synctex_next_line instead. + * On return, the scanner cursor is unchanged if a string could not be scanned or + * points to the terminating '\n' character otherwise. As a consequence, + * _synctex_next_line is necessary after. + * If either scanner or value_ref is NULL, it is considered as an error and + * SYNCTEX_STATUS_BAD_ARGUMENT is returned. + */ +static synctex_ss_s _synctex_decode_string(synctex_scanner_p scanner) { + char * end = NULL; + size_t len = 0;/* The number of bytes to copy */ + size_t already_len = 0; + synctex_zs_s zs = {0,0}; + char * string = NULL; + if (NULL == scanner) { + return (synctex_ss_s){NULL,SYNCTEX_STATUS_BAD_ARGUMENT}; + } + /* The buffer must at least contain one character: the '\n' end of line marker */ + if (SYNCTEX_CUR>=SYNCTEX_END) { +more_characters: + zs = _synctex_buffer_get_available_size(scanner,1); + if (zs.status < SYNCTEX_STATUS_EOF) { + return (synctex_ss_s){NULL,zs.status}; + } else if (0 == zs.size) { + return (synctex_ss_s){NULL,SYNCTEX_STATUS_EOF}; + } + } + /* Now we are sure that there is at least one available character, either because + * SYNCTEX_CUR was already < SYNCTEX_END, or because the buffer has been properly filled. */ + /* end will point to the next unparsed '\n' character in the file, when mapped to the buffer. */ + end = SYNCTEX_CUR; + /* We scan all the characters up to the next '\n' */ + while (end<SYNCTEX_END && *end != '\n') { + ++end; + } + /* OK, we found where to stop: + * either end == SYNCTEX_END + * or *end == '\n' */ + len = end - SYNCTEX_CUR; + if (len<UINT_MAX-already_len) { + if ((string = realloc(string,len+already_len+1)) != NULL) { + if (memcpy(string+already_len,SYNCTEX_CUR,len)) { + already_len += len; + string[already_len]='\0'; /* Terminate the string */ + SYNCTEX_CUR += len;/* Eventually advance to the terminating '\n' */ + if (SYNCTEX_CUR==SYNCTEX_END) { + /* No \n found*/ + goto more_characters; + } + /* trim the trailing whites */ + len = already_len; + while (len>0) { + already_len = len--; + if (string[len]!=' ') { + break; + } + } + string[already_len] = '\0'; + return (synctex_ss_s){string,SYNCTEX_STATUS_OK}; + } + free(string); + _synctex_error("could not copy memory (1)."); + return (synctex_ss_s){NULL,SYNCTEX_STATUS_ERROR}; + } + } + _synctex_error("could not (re)allocate memory (1)."); + return (synctex_ss_s){NULL,SYNCTEX_STATUS_ERROR}; +} + +/* Used when parsing the synctex file. + * Read an Input record. + * - parameter scanner: non NULL scanner + * - returns SYNCTEX_STATUS_OK on successful completions, others values otherwise. + */ +static synctex_ns_s __synctex_parse_new_input(synctex_scanner_p scanner) { + synctex_node_p input = NULL; + synctex_status_t status = SYNCTEX_STATUS_BAD_ARGUMENT; + synctex_zs_s zs = {0,0}; + if (NULL == scanner) { + return (synctex_ns_s){NULL,status}; + } + if ((status=_synctex_match_string(scanner,SYNCTEX_INPUT_MARK))<SYNCTEX_STATUS_OK) { + return (synctex_ns_s){NULL,status}; + } + /* Create a node */ + if (NULL == (input = _synctex_new_input(scanner))) { + _synctex_error("Could not create an input node."); + return (synctex_ns_s){NULL,SYNCTEX_STATUS_ERROR}; + } + /* Decode the tag */ + if ((status=_synctex_data_decode_tag(input))<SYNCTEX_STATUS_OK) { + _synctex_error("Bad format of input node."); + synctex_node_free(input); + return (synctex_ns_s){NULL,status}; + } + /* The next character is a field separator, we expect one character in the buffer. */ + zs = _synctex_buffer_get_available_size(scanner, 1); + if (zs.status<=SYNCTEX_STATUS_ERROR) { + return (synctex_ns_s){NULL,status}; + } + if (0 == zs.size) { + return (synctex_ns_s){NULL,SYNCTEX_STATUS_EOF}; + } + /* We can now safely advance to the next character, stepping over the field separator. */ + ++SYNCTEX_CUR; + --zs.size; + /* Then we scan the file name */ + if ((status=_synctex_data_decode_name(input))<SYNCTEX_STATUS_OK) { + synctex_node_free(input); + _synctex_next_line(scanner);/* Ignore this whole line */ + return (synctex_ns_s){NULL,status}; + } + /* Prepend this input node to the input linked list of the scanner */ + __synctex_tree_set_sibling(input,scanner->input);/* input has no parent */ + scanner->input = input; +# if SYNCTEX_VERBOSE + synctex_node_log(input); +# endif + return (synctex_ns_s){input,_synctex_next_line(scanner)};/* read the line termination character, if any */ +} + +typedef synctex_is_s (*synctex_decoder_t)(synctex_scanner_p); + +/* Used when parsing the synctex file. + * Read one of the settings. + * On normal completion, returns SYNCTEX_STATUS_OK. + * On error, returns SYNCTEX_STATUS_ERROR. + * Both arguments must not be NULL. + * On return, the scanner points to the next character after the decoded object whatever it is. + * It is the responsibility of the caller to prepare the scanner for the next line. + */ +static synctex_status_t _synctex_scan_named(synctex_scanner_p scanner,const char * name) { + synctex_status_t status = 0; + if (NULL == scanner || NULL == name) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } +not_found: + status = _synctex_match_string(scanner,name); + if (status<SYNCTEX_STATUS_NOT_OK) { + return status; + } else if (status == SYNCTEX_STATUS_NOT_OK) { + status = _synctex_next_line(scanner); + if (status<SYNCTEX_STATUS_OK) { + return status; + } + goto not_found; + } + return SYNCTEX_STATUS_OK; +} + +/* Used when parsing the synctex file. + * Read the preamble. + */ +static synctex_status_t _synctex_scan_preamble(synctex_scanner_p scanner) { + synctex_status_t status = 0; + synctex_is_s is = {0,0}; + synctex_ss_s ss = {NULL,0}; + if (NULL == scanner) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + status = _synctex_scan_named(scanner,"SyncTeX Version:"); + if (status<SYNCTEX_STATUS_OK) { + return status; + } + is = _synctex_decode_int(scanner); + if (is.status<SYNCTEX_STATUS_OK) { + return is.status; + } + status = _synctex_next_line(scanner); + if (status<SYNCTEX_STATUS_OK) { + return status; + } + scanner->version = is.integer; + /* Read all the input records */ + do { + status = __synctex_parse_new_input(scanner).status; + if (status<SYNCTEX_STATUS_NOT_OK) { + return status; + } + } while(status == SYNCTEX_STATUS_OK); + /* the loop exits when status == SYNCTEX_STATUS_NOT_OK */ + /* Now read all the required settings. */ + if ((status=_synctex_scan_named(scanner,"Output:"))<SYNCTEX_STATUS_OK) { + return status; + } + if ((ss=_synctex_decode_string(scanner)).status<SYNCTEX_STATUS_OK) { + return is.status; + } + if ((status=_synctex_next_line(scanner))<SYNCTEX_STATUS_OK) { + return status; + } + scanner->output_fmt = ss.string; + if ((status=_synctex_scan_named(scanner,"Magnification:"))<SYNCTEX_STATUS_OK) { + return status; + } + if ((is=_synctex_decode_int(scanner)).status<SYNCTEX_STATUS_OK) { + return is.status; + } + if ((status=_synctex_next_line(scanner))<SYNCTEX_STATUS_OK) { + return status; + } + scanner->pre_magnification = is.integer; + if ((status=_synctex_scan_named(scanner,"Unit:"))<SYNCTEX_STATUS_OK) { + return status; + } + if ((is=_synctex_decode_int(scanner)).status<SYNCTEX_STATUS_OK) { + return is.status; + } + if ((status=_synctex_next_line(scanner))<SYNCTEX_STATUS_OK) { + return status; + } + scanner->pre_unit = is.integer; + if ((status=_synctex_scan_named(scanner,"X Offset:"))<SYNCTEX_STATUS_OK) { + return status; + } + if ((is=_synctex_decode_int(scanner)).status<SYNCTEX_STATUS_OK) { + return is.status; + } + if ((status=_synctex_next_line(scanner))<SYNCTEX_STATUS_OK) { + return status; + } + scanner->pre_x_offset = is.integer; + if ((status=_synctex_scan_named(scanner,"Y Offset:"))<SYNCTEX_STATUS_OK) { + return status; + } + if ((is=_synctex_decode_int(scanner)).status<SYNCTEX_STATUS_OK) { + return is.status; + } + if ((status=_synctex_next_line(scanner))<SYNCTEX_STATUS_OK) { + return status; + } + scanner->pre_y_offset = is.integer; + return SYNCTEX_STATUS_OK; +} + +/* parse a float with a dimension */ +static synctex_fs_s _synctex_scan_float_and_dimension(synctex_scanner_p scanner) { + synctex_fs_s fs = {0,0}; + synctex_zs_s zs = {0,0}; + char * endptr = NULL; +#ifdef HAVE_SETLOCALE + char * loc = setlocale(LC_NUMERIC, NULL); +#endif + if (NULL == scanner) { + return (synctex_fs_s){0,SYNCTEX_STATUS_BAD_ARGUMENT}; + } + zs = _synctex_buffer_get_available_size(scanner, SYNCTEX_BUFFER_MIN_SIZE); + if (zs.status<SYNCTEX_STATUS_EOF) { + _synctex_error("Problem with float."); + return (synctex_fs_s){0,zs.status}; + } +#ifdef HAVE_SETLOCALE + setlocale(LC_NUMERIC, "C"); +#endif + fs.value = strtod(SYNCTEX_CUR,&endptr); +#ifdef HAVE_SETLOCALE + setlocale(LC_NUMERIC, loc); +#endif + if (endptr == SYNCTEX_CUR) { + _synctex_error("A float was expected."); + return (synctex_fs_s){0,SYNCTEX_STATUS_ERROR}; + } + SYNCTEX_CUR = endptr; + if ((fs.status = _synctex_match_string(scanner,"in")) >= SYNCTEX_STATUS_OK) { + fs.value *= 72.27f*65536; + } else if (fs.status<SYNCTEX_STATUS_EOF) { + report_unit_error: + _synctex_error("problem with unit."); + return fs; + } else if ((fs.status = _synctex_match_string(scanner,"cm")) >= SYNCTEX_STATUS_OK) { + fs.value *= 72.27f*65536/2.54f; + } else if (fs.status<SYNCTEX_STATUS_EOF) { + goto report_unit_error; + } else if ((fs.status = _synctex_match_string(scanner,"mm")) >= SYNCTEX_STATUS_OK) { + fs.value *= 72.27f*65536/25.4f; + } else if (fs.status<SYNCTEX_STATUS_EOF) { + goto report_unit_error; + } else if ((fs.status = _synctex_match_string(scanner,"pt")) >= SYNCTEX_STATUS_OK) { + fs.value *= 65536.0f; + } else if (fs.status<SYNCTEX_STATUS_EOF) { + goto report_unit_error; + } else if ((fs.status = _synctex_match_string(scanner,"bp")) >= SYNCTEX_STATUS_OK) { + fs.value *= 72.27f/72*65536.0f; + } else if (fs.status<SYNCTEX_STATUS_EOF) { + goto report_unit_error; + } else if ((fs.status = _synctex_match_string(scanner,"pc")) >= SYNCTEX_STATUS_OK) { + fs.value *= 12.0*65536.0f; + } else if (fs.status<SYNCTEX_STATUS_EOF) { + goto report_unit_error; + } else if ((fs.status = _synctex_match_string(scanner,"sp")) >= SYNCTEX_STATUS_OK) { + fs.value *= 1.0f; + } else if (fs.status<SYNCTEX_STATUS_EOF) { + goto report_unit_error; + } else if ((fs.status = _synctex_match_string(scanner,"dd")) >= SYNCTEX_STATUS_OK) { + fs.value *= 1238.0f/1157*65536.0f; + } else if (fs.status<SYNCTEX_STATUS_EOF) { + goto report_unit_error; + } else if ((fs.status = _synctex_match_string(scanner,"cc")) >= SYNCTEX_STATUS_OK) { + fs.value *= 14856.0f/1157*65536; + } else if (fs.status<SYNCTEX_STATUS_EOF) { + goto report_unit_error; + } else if ((fs.status = _synctex_match_string(scanner,"nd")) >= SYNCTEX_STATUS_OK) { + fs.value *= 685.0f/642*65536; + } else if (fs.status<SYNCTEX_STATUS_EOF) { + goto report_unit_error; + } else if ((fs.status = _synctex_match_string(scanner,"nc")) >= SYNCTEX_STATUS_OK) { + fs.value *= 1370.0f/107*65536; + } else if (fs.status<SYNCTEX_STATUS_EOF) { + goto report_unit_error; + } + return fs; +} + +/* parse the post scriptum + * SYNCTEX_STATUS_OK is returned on completion + * a negative error is returned otherwise */ +static synctex_status_t _synctex_scan_post_scriptum(synctex_scanner_p scanner) { + synctex_status_t status = 0; + synctex_fs_s fs = {0,0}; + char * endptr = NULL; +#ifdef HAVE_SETLOCALE + char * loc = setlocale(LC_NUMERIC, NULL); +#endif + if (NULL == scanner) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + /* Scan the file until a post scriptum line is found */ +post_scriptum_not_found: + status = _synctex_match_string(scanner,"Post scriptum:"); + if (status<SYNCTEX_STATUS_NOT_OK) { + return status; + } + if (status == SYNCTEX_STATUS_NOT_OK) { + status = _synctex_next_line(scanner); + if (status<SYNCTEX_STATUS_EOF) { + return status; + } else if (status<SYNCTEX_STATUS_OK) { + return SYNCTEX_STATUS_OK;/* The EOF is found, we have properly scanned the file */ + } + goto post_scriptum_not_found; + } + /* We found the name, advance to the next line. */ +next_line: + status = _synctex_next_line(scanner); + if (status<SYNCTEX_STATUS_EOF) { + return status; + } else if (status<SYNCTEX_STATUS_OK) { + return SYNCTEX_STATUS_OK;/* The EOF is found, we have properly scanned the file */ + } + /* Scanning the information */ + status = _synctex_match_string(scanner,"Magnification:"); + if (status == SYNCTEX_STATUS_OK ) { +#ifdef HAVE_SETLOCALE + setlocale(LC_NUMERIC, "C"); +#endif + scanner->unit = strtod(SYNCTEX_CUR,&endptr); +#ifdef HAVE_SETLOCALE + setlocale(LC_NUMERIC, loc); +#endif + if (endptr == SYNCTEX_CUR) { + _synctex_error("bad magnification in the post scriptum, a float was expected."); + return SYNCTEX_STATUS_ERROR; + } + if (scanner->unit<=0) { + _synctex_error("bad magnification in the post scriptum, a positive float was expected."); + return SYNCTEX_STATUS_ERROR; + } + SYNCTEX_CUR = endptr; + goto next_line; + } + if (status<SYNCTEX_STATUS_EOF){ + report_record_problem: + _synctex_error("Problem reading the Post Scriptum records"); + return status; /* echo the error. */ + } + status = _synctex_match_string(scanner,"X Offset:"); + if (status == SYNCTEX_STATUS_OK) { + fs = _synctex_scan_float_and_dimension(scanner); + if (fs.status<SYNCTEX_STATUS_OK) { + _synctex_error("Problem with X offset in the Post Scriptum."); + return fs.status; + } + scanner->x_offset = fs.value; + goto next_line; + } else if (status<SYNCTEX_STATUS_EOF){ + goto report_record_problem; + } + status = _synctex_match_string(scanner,"Y Offset:"); + if (status==SYNCTEX_STATUS_OK) { + fs = _synctex_scan_float_and_dimension(scanner); + if (fs.status<SYNCTEX_STATUS_OK) { + _synctex_error("Problem with Y offset in the Post Scriptum."); + return fs.status; + } + scanner->x_offset = fs.value; + goto next_line; + } else if (status<SYNCTEX_STATUS_EOF){ + goto report_record_problem; + } + goto next_line; +} + +/* SYNCTEX_STATUS_OK is returned if the postamble is read + * SYNCTEX_STATUS_NOT_OK is returned if the postamble is not at the current location + * a negative error otherwise + * The postamble comprises the post scriptum section. + */ +static synctex_status_t _synctex_scan_postamble(synctex_scanner_p scanner) { + synctex_status_t status = 0; + synctex_is_s is = {0,0}; + if (NULL == scanner) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + if (!scanner->flags.postamble && (status=_synctex_match_string(scanner,"Postamble:"))<SYNCTEX_STATUS_OK) { + return status; + } +count_again: + if ((status=_synctex_next_line(scanner))<SYNCTEX_STATUS_OK) { + return status; + } + if ((status=_synctex_scan_named(scanner,"Count:"))< SYNCTEX_STATUS_EOF) { + return status; /* forward the error */ + } else if (status < SYNCTEX_STATUS_OK) { /* No Count record found */ + goto count_again; + } + if ((is=_synctex_decode_int(scanner)).status<SYNCTEX_STATUS_OK) { + return is.status; + } + if ((status=_synctex_next_line(scanner))<SYNCTEX_STATUS_OK) { + return status; + } + scanner->count = is.integer; + /* Now we scan the last part of the SyncTeX file: the Post Scriptum section. */ + return _synctex_scan_post_scriptum(scanner); +} + +/* Horizontal boxes also have visible size. + * Visible size are bigger than real size. + * For example 0 width boxes may contain text. + * At creation time, the visible size is set to the values of the real size. + */ +static synctex_status_t _synctex_setup_visible_hbox(synctex_node_p box) { + if (box) { + switch(synctex_node_type(box)) { + case synctex_node_type_hbox: + _synctex_data_set_h_V(box,_synctex_data_h(box)); + _synctex_data_set_v_V(box,_synctex_data_v(box)); + _synctex_data_set_width_V(box,_synctex_data_width(box)); + _synctex_data_set_height_V(box,_synctex_data_height(box)); + _synctex_data_set_depth_V(box,_synctex_data_depth(box)); + return SYNCTEX_STATUS_OK; + default: + break; + } + } + return SYNCTEX_STATUS_BAD_ARGUMENT; +} + +/* This method is sent to an horizontal box to setup the visible size + * Some box have 0 width but do contain text material. + * With this method, one can enlarge the box to contain the given point (h,v). + */ +static synctex_status_t _synctex_make_hbox_contain_point(synctex_node_p node,synctex_point_s point) { + int min, max, n; + if (NULL == node || synctex_node_type(node) != synctex_node_type_hbox) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + if ((n = _synctex_data_width_V(node))<0) { + max = _synctex_data_h_V(node); + min = max+n; + if (point.h<min) { + _synctex_data_set_width_V(node,point.h-max); + } else if (point.h>max) { + _synctex_data_set_h_V(node,point.h); + _synctex_data_set_width_V(node,min-point.h); + } + } else { + min = _synctex_data_h_V(node); + max = min+n; + if (point.h<min) { + _synctex_data_set_h_V(node,point.h); + _synctex_data_set_width_V(node,max - point.h); + } else if (point.h>max) { + _synctex_data_set_width_V(node,point.h - min); + } + } + n = _synctex_data_v_V(node); + min = n - _synctex_data_height_V(node); + max = n + _synctex_data_depth_V(node); + if (point.v<min) { + _synctex_data_set_height_V(node,n-point.v); + } else if (point.v>max) { + _synctex_data_set_depth_V(node,point.v-n); + } + return SYNCTEX_STATUS_OK; +} +static synctex_status_t _synctex_make_hbox_contain_box(synctex_node_p node,synctex_box_s box) { + int min, max, n; + if (NULL == node || synctex_node_type(node) != synctex_node_type_hbox) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + if ((n = _synctex_data_width_V(node))<0) { + max = _synctex_data_h_V(node); + min = max+n; + if (box.min.h <min) { + _synctex_data_set_width_V(node,box.min.h-max); + } else if (box.max.h>max) { + _synctex_data_set_h_V(node,box.max.h); + _synctex_data_set_width_V(node,min-box.max.h); + } + } else { + min = _synctex_data_h_V(node); + max = min+n; + if (box.min.h<min) { + _synctex_data_set_h_V(node,box.min.h); + _synctex_data_set_width_V(node,max - box.min.h); + } else if (box.max.h>max) { + _synctex_data_set_width_V(node,box.max.h - min); + } + } + n = _synctex_data_v_V(node); + min = n - _synctex_data_height_V(node); + max = n + _synctex_data_depth_V(node); + if (box.min.v<min) { + _synctex_data_set_height_V(node,n-box.min.v); + } else if (box.max.v>max) { + _synctex_data_set_depth_V(node,box.max.v-n); + } + return SYNCTEX_STATUS_OK; +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark SPECIAL CHARACTERS +# endif + + +/* Here are the control characters that start each line of the synctex output file. + * Their values define the meaning of the line. + */ +# define SYNCTEX_CHAR_BEGIN_SHEET '{' +# define SYNCTEX_CHAR_END_SHEET '}' +# define SYNCTEX_CHAR_BEGIN_FORM '<' +# define SYNCTEX_CHAR_END_FORM '>' +# define SYNCTEX_CHAR_BEGIN_VBOX '[' +# define SYNCTEX_CHAR_END_VBOX ']' +# define SYNCTEX_CHAR_BEGIN_HBOX '(' +# define SYNCTEX_CHAR_END_HBOX ')' +# define SYNCTEX_CHAR_ANCHOR '!' +# define SYNCTEX_CHAR_VOID_VBOX 'v' +# define SYNCTEX_CHAR_VOID_HBOX 'h' +# define SYNCTEX_CHAR_KERN 'k' +# define SYNCTEX_CHAR_GLUE 'g' +# define SYNCTEX_CHAR_RULE 'r' +# define SYNCTEX_CHAR_MATH '$' +# define SYNCTEX_CHAR_FORM_REF 'f' +# define SYNCTEX_CHAR_BOUNDARY 'x' +# define SYNCTEX_CHAR_CHARACTER 'c' +# define SYNCTEX_CHAR_COMMENT '%' + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark SCANNERS & PARSERS +# endif + +# define SYNCTEX_DECODE_FAILED(NODE,WHAT) \ +(_synctex_data_decode_##WHAT(NODE)<SYNCTEX_STATUS_OK) +# define SYNCTEX_DECODE_FAILED_V(NODE,WHAT) \ +(_synctex_data_decode_##WHAT##_v(NODE)<SYNCTEX_STATUS_OK) + +#define SYNCTEX_NS_NULL (synctex_ns_s){NULL,SYNCTEX_STATUS_NOT_OK} +static synctex_ns_s _synctex_parse_new_sheet(synctex_scanner_p scanner) { + synctex_node_p node; + if ((node = _synctex_new_sheet(scanner))) { + if ( + SYNCTEX_DECODE_FAILED(node,page)) { + _synctex_error("Bad sheet record."); + } else if (_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Missing end of sheet."); + } else { + /* Now set the owner */ + if (scanner->sheet) { + synctex_node_p last_sheet = scanner->sheet; + synctex_node_p next_sheet = NULL; + while ((next_sheet = __synctex_tree_sibling(last_sheet))) { + last_sheet = next_sheet; + } + /* sheets have no parent */ + __synctex_tree_set_sibling(last_sheet,node); + } else { + scanner->sheet = node; + } + return (synctex_ns_s){node,SYNCTEX_STATUS_OK}; + } + _synctex_free_node(node); + } + return (synctex_ns_s){NULL,SYNCTEX_STATUS_ERROR}; +} +/** + * - requirement: scanner != NULL + */ +static synctex_ns_s _synctex_parse_new_form(synctex_scanner_p scanner) { + synctex_node_p node; + if ((node = _synctex_new_form(scanner))) { + if ( + SYNCTEX_DECODE_FAILED(node,tag)) { + _synctex_error("Bad sheet record."); + } else if (_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Missing end of form."); + } else { + /* Now set the owner */ + if (scanner->form) { + synctex_node_p last_form = scanner->form; + synctex_node_p next_form = NULL; + while ((next_form = __synctex_tree_sibling(last_form))) { + last_form = next_form; + } + __synctex_tree_set_sibling(last_form,node); + } else { + scanner->form = node; + } + return (synctex_ns_s){node,SYNCTEX_STATUS_OK}; + } + _synctex_free_node(node); + } + return (synctex_ns_s){NULL,SYNCTEX_STATUS_ERROR}; +} +# define SYNCTEX_SHOULD_DECODE_FAILED(NODE,WHAT) \ +(_synctex_data_has_##WHAT(NODE) &&(_synctex_data_decode_##WHAT(NODE)<SYNCTEX_STATUS_OK)) +# define SYNCTEX_SHOULD_DECODE_FAILED_V(NODE,WHAT) \ +(_synctex_data_has_##WHAT(NODE) &&(_synctex_data_decode_##WHAT##_v(NODE)<SYNCTEX_STATUS_OK)) + +static synctex_status_t _synctex_data_decode_tlchvwhd(synctex_node_p node) { + return SYNCTEX_SHOULD_DECODE_FAILED(node,tag) + || SYNCTEX_SHOULD_DECODE_FAILED(node,line) + || SYNCTEX_SHOULD_DECODE_FAILED(node,column) + || SYNCTEX_SHOULD_DECODE_FAILED(node,h) + || SYNCTEX_SHOULD_DECODE_FAILED_V(node,v) + || SYNCTEX_SHOULD_DECODE_FAILED(node,width) + || SYNCTEX_SHOULD_DECODE_FAILED(node,height) + || SYNCTEX_SHOULD_DECODE_FAILED(node,depth); +} +static synctex_ns_s _synctex_parse_new_vbox(synctex_scanner_p scanner) { + synctex_node_p node; + if ((node = _synctex_new_vbox(scanner))) { + if (_synctex_data_decode_tlchvwhd(node)) { + _synctex_error("Bad vbox record."); + _synctex_next_line(scanner); + out: + _synctex_free_node(node); + return (synctex_ns_s){NULL,SYNCTEX_STATUS_ERROR}; + } + if (_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Missing end of vbox."); + goto out; + } + return (synctex_ns_s){node,SYNCTEX_STATUS_OK}; + } + _synctex_next_line(scanner); + return (synctex_ns_s){NULL,SYNCTEX_STATUS_ERROR}; +} +SYNCTEX_INLINE static synctex_node_p __synctex_node_make_friend_tlc(synctex_node_p node); +static synctex_ns_s _synctex_parse_new_hbox(synctex_scanner_p scanner) { + synctex_node_p node; + if ((node = _synctex_new_hbox(scanner))) { + if (_synctex_data_decode_tlchvwhd(node)) { + _synctex_error("Bad hbox record."); + _synctex_next_line(scanner); + out: + _synctex_free_node(node); + return (synctex_ns_s){NULL,SYNCTEX_STATUS_ERROR}; + } + if (_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Missing end of hbox."); + goto out; + } + if (_synctex_setup_visible_hbox(node)<SYNCTEX_STATUS_OK) { + _synctex_error("Unexpected error (_synctex_parse_new_hbox)."); + goto out; + } + return (synctex_ns_s){node,SYNCTEX_STATUS_OK}; + } + _synctex_next_line(scanner); + return (synctex_ns_s){NULL,SYNCTEX_STATUS_ERROR}; +} +static synctex_ns_s _synctex_parse_new_void_vbox(synctex_scanner_p scanner) { + synctex_node_p node; + if ((node = _synctex_new_void_vbox(scanner))) { + if (_synctex_data_decode_tlchvwhd(node)) { + _synctex_error("Bad void vbox record."); + _synctex_next_line(scanner); + out: + _synctex_free_node(node); + return (synctex_ns_s){NULL,SYNCTEX_STATUS_ERROR}; + } + if (_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Missing end of container."); + goto out; + } + return (synctex_ns_s){node,SYNCTEX_STATUS_OK}; + } + _synctex_next_line(scanner); + return (synctex_ns_s){NULL,SYNCTEX_STATUS_ERROR}; +} +static synctex_ns_s _synctex_parse_new_void_hbox(synctex_scanner_p scanner) { + synctex_node_p node; + if ((node = _synctex_new_void_hbox(scanner))) { + if (_synctex_data_decode_tlchvwhd(node)) { + _synctex_error("Bad void hbox record."); + _synctex_next_line(scanner); + out: + _synctex_free_node(node); + return (synctex_ns_s){NULL,SYNCTEX_STATUS_ERROR}; + } + if (_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Missing end of container."); + goto out; + } + return (synctex_ns_s){node,SYNCTEX_STATUS_OK}; + } + _synctex_next_line(scanner); + return (synctex_ns_s){NULL,SYNCTEX_STATUS_ERROR}; +} +static synctex_ns_s _synctex_parse_new_kern(synctex_scanner_p scanner) { + synctex_node_p node; + if ((node = _synctex_new_kern(scanner))) { + if (_synctex_data_decode_tlchvwhd(node)) { + _synctex_error("Bad kern record."); + _synctex_next_line(scanner); + out: + _synctex_free_node(node); + return (synctex_ns_s){NULL,SYNCTEX_STATUS_ERROR}; + } + if (_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Missing end of container."); + goto out; + } + return (synctex_ns_s){node,SYNCTEX_STATUS_OK}; + } + _synctex_next_line(scanner); + return (synctex_ns_s){NULL,SYNCTEX_STATUS_ERROR}; +} +static synctex_ns_s _synctex_parse_new_glue(synctex_scanner_p scanner) { + synctex_node_p node; + if ((node = _synctex_new_glue(scanner))) { + if (_synctex_data_decode_tlchvwhd(node)) { + _synctex_error("Bad glue record."); + _synctex_next_line(scanner); + out: + _synctex_free_node(node); + return (synctex_ns_s){NULL,SYNCTEX_STATUS_ERROR}; + } + if (_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Missing end of container."); + goto out; + } + return (synctex_ns_s){node,SYNCTEX_STATUS_OK}; + } + _synctex_next_line(scanner); + return (synctex_ns_s){NULL,SYNCTEX_STATUS_ERROR}; +} +static synctex_ns_s _synctex_parse_new_rule(synctex_scanner_p scanner) { + synctex_node_p node; + if ((node = _synctex_new_rule(scanner))) { + if (_synctex_data_decode_tlchvwhd(node)) { + _synctex_error("Bad rule record."); + _synctex_next_line(scanner); + out: + _synctex_free_node(node); + return (synctex_ns_s){NULL,SYNCTEX_STATUS_ERROR}; + } + if (_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Missing end of container."); + goto out; + } + return (synctex_ns_s){node,SYNCTEX_STATUS_OK}; + } + _synctex_next_line(scanner); + return (synctex_ns_s){NULL,SYNCTEX_STATUS_ERROR}; +} +static synctex_ns_s _synctex_parse_new_math(synctex_scanner_p scanner) { + synctex_node_p node; + if ((node = _synctex_new_math(scanner))) { + if (_synctex_data_decode_tlchvwhd(node)) { + _synctex_error("Bad math record."); + _synctex_next_line(scanner); + out: + _synctex_free_node(node); + return (synctex_ns_s){NULL,SYNCTEX_STATUS_ERROR}; + } + if (_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Missing end of container."); + goto out; + } + return (synctex_ns_s){node,SYNCTEX_STATUS_OK}; + } + _synctex_next_line(scanner); + return (synctex_ns_s){NULL,SYNCTEX_STATUS_ERROR}; +} +static synctex_ns_s _synctex_parse_new_boundary(synctex_scanner_p scanner) { + synctex_node_p node; + if ((node = _synctex_new_boundary(scanner))) { + if (_synctex_data_decode_tlchvwhd(node)) { + _synctex_error("Bad boundary record."); + _synctex_next_line(scanner); + out: + _synctex_free_node(node); + return (synctex_ns_s){NULL,SYNCTEX_STATUS_ERROR}; + } + if (_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Missing end of container."); + goto out; + } + return (synctex_ns_s){node,SYNCTEX_STATUS_OK}; + } + _synctex_next_line(scanner); + return (synctex_ns_s){NULL,SYNCTEX_STATUS_ERROR}; +} +SYNCTEX_INLINE static synctex_ns_s _synctex_parse_new_ref(synctex_scanner_p scanner) { + synctex_node_p node; + if ((node = _synctex_new_ref(scanner))) { + if (SYNCTEX_DECODE_FAILED(node,tag) + || SYNCTEX_DECODE_FAILED(node,h) + || SYNCTEX_DECODE_FAILED_V(node,v)) { + _synctex_error("Bad form ref record."); + _synctex_next_line(scanner); + out: + _synctex_free_node(node); + return (synctex_ns_s){NULL,SYNCTEX_STATUS_ERROR}; + } + if (_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Missing end of container."); + goto out; + } + return (synctex_ns_s){node,SYNCTEX_STATUS_OK}; + } + _synctex_next_line(scanner); + return (synctex_ns_s){NULL,SYNCTEX_STATUS_ERROR}; +} +# undef SYNCTEX_DECODE_FAILED +# undef SYNCTEX_DECODE_FAILED_V + +SYNCTEX_INLINE static synctex_point_s _synctex_data_point(synctex_node_p node); +SYNCTEX_INLINE static synctex_point_s _synctex_data_point_V(synctex_node_p node); +SYNCTEX_INLINE static synctex_point_s _synctex_data_set_point(synctex_node_p node, synctex_point_s point); +SYNCTEX_INLINE static synctex_box_s _synctex_data_box(synctex_node_p node); +SYNCTEX_INLINE static synctex_box_s _synctex_data_xob(synctex_node_p node); +SYNCTEX_INLINE static synctex_box_s _synctex_data_box_V(synctex_node_p node); + +SYNCTEX_INLINE static synctex_node_p _synctex_input_register_line(synctex_node_p input,synctex_node_p node) { + if (node && _synctex_data_tag(input) != _synctex_data_tag(node)) { + input = synctex_scanner_input_with_tag(node->class_->scanner,_synctex_data_tag(node)); + } + if (_synctex_data_line(node)>_synctex_data_line(input)) { + _synctex_data_set_line(input,_synctex_data_line(node)); + } + return input; +} +/** + * Free node and its siblings and return its detached child. + */ +SYNCTEX_INLINE static synctex_node_p _synctex_handle_pop_child(synctex_node_p handle) { + synctex_node_p child = _synctex_tree_reset_child(handle); + synctex_node_free(handle); + return child; +} +/** + * Set the tlc of all the x nodes that are targets of + * x_handle and its sibling. + * Reset the target of x_handle and deletes its siblings. + * child is a node that has just been parsed and is not a boundary node. + */ +SYNCTEX_INLINE static void _synctex_handle_set_tlc(synctex_node_p x_handle, synctex_node_p child, synctex_bool_t make_friend) { + if (x_handle) { + synctex_node_p sibling = x_handle; + if (child) { + synctex_node_p target; + while ((target = synctex_node_target(sibling))) { + _synctex_data_set_tlc(target,child); + if (make_friend) { + _synctex_node_make_friend_tlc(target); + } + if ((sibling = __synctex_tree_sibling(sibling))) { + continue; + } else { + break; + } + } + } + _synctex_tree_reset_target(x_handle); + sibling = __synctex_tree_reset_sibling(x_handle); + synctex_node_free(sibling); + } +} +/** + * When we have parsed a box, we must register + * all the contained heading boundary nodes + * that have not yet been registered. + * Those handles will be deleted when popping. + */ +SYNCTEX_INLINE static void _synctex_handle_make_friend_tlc(synctex_node_p node) { + while (node) { + synctex_node_p target = _synctex_tree_reset_target(node); + _synctex_node_make_friend_tlc(target); + node = __synctex_tree_sibling(node); + } +} +/** + * Scan sheets, forms and input records. + * - parameter scanner: owning scanner + * - returns: status + */ +static synctex_status_t __synctex_parse_sfi(synctex_scanner_p scanner) { + synctex_status_t status = SYNCTEX_STATUS_OK; + synctex_zs_s zs = {0,0}; + synctex_ns_s input = SYNCTEX_NS_NULL; + synctex_node_p sheet = NULL; + synctex_node_p form = NULL; + synctex_node_p parent = NULL; + synctex_node_p child = NULL; + /* + * Experimentations lead to the forthcoming conclusion: + * Sometimes, the first nodes of a box have the wrong line number. + * These are only boundary (x) nodes. + * We observed that boundary nodes do have the proper line number + * if they follow a node with a different type. + * We keep track of these leading x nodes in a handle tree. + */ + synctex_node_p x_handle = NULL; +# define SYNCTEX_RETURN(STATUS) \ + synctex_node_free(x_handle);\ + return STATUS + synctex_node_p last_k = NULL; + synctex_node_p last_g = NULL; + synctex_ns_s ns = SYNCTEX_NS_NULL; + int form_depth = 0; + int ignored_form_depth = 0; + synctex_bool_t try_input = synctex_YES; + if (!(x_handle = _synctex_new_handle(scanner))) { + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } +# ifdef SYNCTEX_NOTHING +# pragma mark MAIN LOOP +# endif +main_loop: + status = SYNCTEX_STATUS_OK; + sheet = form = parent = child = NULL; +# define SYNCTEX_START_SCAN(WHAT)\ +(*SYNCTEX_CUR == SYNCTEX_CHAR_##WHAT) + if (SYNCTEX_CUR<SYNCTEX_END) { + if (SYNCTEX_START_SCAN(BEGIN_FORM)) { +# ifdef SYNCTEX_NOTHING +# pragma mark + SCAN FORM +# endif + scan_form: + ns = _synctex_parse_new_form(scanner); + if (ns.status == SYNCTEX_STATUS_OK) { + ++form_depth; + if (_synctex_tree_parent(form)) { + /* This form is already being parsed */ + ++ignored_form_depth; + goto ignore_loop; + } + _synctex_tree_set_parent(ns.node,form); + form = ns.node; + parent = form; + child = NULL; + last_k = last_g = NULL; + goto content_loop; + } + if (form || sheet) { + last_k = last_g = NULL; + goto content_loop; + } + try_input = synctex_YES; + goto main_loop; + } else if (SYNCTEX_START_SCAN(BEGIN_SHEET)) { +# ifdef SYNCTEX_NOTHING +# pragma mark + SCAN SHEET +# endif + try_input = synctex_YES; + ns = _synctex_parse_new_sheet(scanner); + if (ns.status == SYNCTEX_STATUS_OK) { + sheet = ns.node; + parent = sheet; + last_k = last_g = NULL; + goto content_loop; + } + goto main_loop; + } else if (SYNCTEX_START_SCAN(ANCHOR)) { +# ifdef SYNCTEX_NOTHING +# pragma mark + SCAN ANCHOR +# endif + scan_anchor: + ++SYNCTEX_CUR; + if (_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Missing anchor."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + if (form || sheet) { + last_k = last_g = NULL; + goto content_loop; + } + try_input = synctex_YES; + goto main_loop; + } else if (SYNCTEX_START_SCAN(ANCHOR)) { +# ifdef SYNCTEX_NOTHING +# pragma mark + SCAN COMMENT +# endif + ++SYNCTEX_CUR; + _synctex_next_line(scanner); + try_input = synctex_YES; + goto main_loop; + } else if (try_input) { +# ifdef SYNCTEX_NOTHING +# pragma mark + SCAN INPUT +# endif + try_input = synctex_NO; + do { + input = __synctex_parse_new_input(scanner); + } while (input.status == SYNCTEX_STATUS_OK); + goto main_loop; + } + status = _synctex_match_string(scanner,"Postamble:"); + if (status==SYNCTEX_STATUS_OK) { + scanner->flags.postamble = 1; + SYNCTEX_RETURN(status); + } + status = _synctex_next_line(scanner); + if (status<SYNCTEX_STATUS_OK) { + SYNCTEX_RETURN(status); + } + } + /* At least 1 more character */ + zs = _synctex_buffer_get_available_size(scanner,1); + if (zs.size == 0){ + _synctex_error("Incomplete synctex file, postamble missing."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + goto main_loop; + /* Unreachable. */ +# ifdef SYNCTEX_NOTHING +# pragma mark IGNORE LOOP +# endif +ignore_loop: + ns = SYNCTEX_NS_NULL; + if (SYNCTEX_CUR<SYNCTEX_END) { + if (SYNCTEX_START_SCAN(BEGIN_FORM)) { + ++ignored_form_depth; + } else if (SYNCTEX_START_SCAN(END_FORM)) { + --ignored_form_depth; + } + if (_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Incomplete container."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + } else { + zs = _synctex_buffer_get_available_size(scanner,1); + if (zs.size == 0){ + _synctex_error("Incomplete synctex file, postamble missing."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + } + if (ignored_form_depth) { + goto ignore_loop; + } else { + last_k = last_g = NULL; + goto content_loop; + } + +# ifdef SYNCTEX_NOTHING +# pragma mark CONTENT LOOP +# endif +content_loop: + /* Either in a form, a sheet or a box. + * - in a sheet, "{" is not possible, only boxes and "}" at top level. + * - in a form, "{" is not possible, only boxes, "<" and ">" at top level. + * - in a box, the unique possibility is '<', '[', '(' or ">". + * We still keep the '(' for a sheet, because that dos not cost too much. + * We must also consider void boxes as children. + */ + /* forms are everywhere */ + ns = SYNCTEX_NS_NULL; +#if SYNCTEX_VERBOSE + synctex_scanner_set_display_switcher(scanner,-1); + printf("NEW CONTENT LOOP\n"); +#if SYNCTEX_DEBUG>500 + synctex_node_display(sheet); +#endif +#endif + if (SYNCTEX_CUR<SYNCTEX_END) { + if (SYNCTEX_START_SCAN(BEGIN_FORM)) { + goto scan_form; + } else if (SYNCTEX_START_SCAN(BEGIN_VBOX)) { +# ifdef SYNCTEX_NOTHING +# pragma mark + SCAN VBOX +# endif + ns = _synctex_parse_new_vbox(scanner); + if (ns.status == SYNCTEX_STATUS_OK) { + x_handle = _synctex_new_handle_with_child(x_handle); + if (child) { + _synctex_node_set_sibling(child,ns.node); + } else { + _synctex_node_set_child(parent,ns.node); + } + parent = ns.node; + child = _synctex_tree_last(parent); +# if SYNCTEX_VERBOSE + synctex_node_log(parent); +# endif + input.node = _synctex_input_register_line(input.node,parent); + last_k = last_g = NULL; + goto content_loop; + } + } else if (SYNCTEX_START_SCAN(END_VBOX)) { + if (synctex_node_type(parent) == synctex_node_type_vbox) { +# ifdef SYNCTEX_NOTHING +# pragma mark + SCAN XOBV +# endif + ++SYNCTEX_CUR; + if (NULL == _synctex_tree_child(parent) && !form) { + /* only void v boxes are friends */ + _synctex_node_make_friend_tlc(parent); + } + child = parent; + parent = _synctex_tree_parent(child); + if (!form) { + _synctex_handle_make_friend_tlc(x_handle); + } + x_handle = _synctex_handle_pop_child(x_handle); + _synctex_handle_set_tlc(x_handle,child,!form); +# if SYNCTEX_VERBOSE + synctex_node_log(child); +# endif + if (_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Incomplete container."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + last_k = last_g = NULL; + goto content_loop; + } + } else if (SYNCTEX_START_SCAN(BEGIN_HBOX)) { +# ifdef SYNCTEX_NOTHING +# pragma mark + SCAN HBOX +# endif +# if defined(SYNCTEX_USE_CHARINDEX) + synctex_charindex_t char_index = (synctex_charindex_t)(scanner->reader->charindex_offset+SYNCTEX_CUR-SYNCTEX_START); + synctex_lineindex_t line_index = scanner->reader->line_number; +# endif + ns = _synctex_parse_new_hbox(scanner); + if (ns.status == SYNCTEX_STATUS_OK) { + x_handle = _synctex_new_handle_with_child(x_handle); + if (child) { + _synctex_node_set_sibling(child,ns.node); + } else { + _synctex_node_set_child(parent,ns.node); + } + parent = ns.node; + /* add a box boundary node at the start */ + if ((child = _synctex_new_box_bdry(scanner))) { +# if defined(SYNCTEX_USE_CHARINDEX) + child->line_index=line_index; + child->char_index=char_index; +# endif + _synctex_node_set_child(parent,child); + _synctex_data_set_tlchv(child,parent); + if (!form) { + __synctex_node_make_friend_tlc(child); + } + } else { + _synctex_error("Can't create box bdry record."); + } +# if SYNCTEX_VERBOSE + synctex_node_log(parent); +# endif + input.node = _synctex_input_register_line(input.node,parent); + last_k = last_g = NULL; + goto content_loop; + } + } else if (SYNCTEX_START_SCAN(END_HBOX)) { + if (synctex_node_type(parent) == synctex_node_type_hbox) { +# ifdef SYNCTEX_NOTHING +# pragma mark + SCAN XOBH +# endif + ++SYNCTEX_CUR; + /* setting the next horizontal box at the end ensures + * that a child is recorded before any of its ancestors. + */ + if (form == NULL /* && sheet != NULL*/ ) { + _synctex_tree_set_next_hbox(parent,_synctex_tree_next_hbox(sheet)); + _synctex_tree_set_next_hbox(sheet,parent); + } + { + /* Update the mean line number */ + synctex_node_p node = _synctex_tree_child(parent); + synctex_node_p sibling = NULL; + /* Ignore the first node (a box_bdry) */ + if (node && (sibling = __synctex_tree_sibling(node))) { + unsigned int node_weight = 0; + unsigned int cumulated_line_numbers = 0; + _synctex_data_set_line(node, _synctex_data_line(sibling)); + node = sibling; + do { + if (synctex_node_type(node)==synctex_node_type_hbox) { + if (_synctex_data_weight(node)) { + node_weight += _synctex_data_weight(node); + cumulated_line_numbers += _synctex_data_mean_line(node)*_synctex_data_weight(node); + } else { + ++node_weight; + cumulated_line_numbers += _synctex_data_mean_line(node); + } + } else { + ++node_weight; + cumulated_line_numbers += synctex_node_line(node); + } + } while ((node = __synctex_tree_sibling(node))); + _synctex_data_set_mean_line(parent,(cumulated_line_numbers + node_weight/2)/node_weight); + _synctex_data_set_weight(parent,node_weight); + } else { + _synctex_data_set_mean_line(parent,_synctex_data_line(parent)); + _synctex_data_set_weight(parent,1); + } + if ((sibling = _synctex_new_box_bdry(scanner))) { +# if defined(SYNCTEX_USE_CHARINDEX) + sibling->line_index=child->line_index; + sibling->char_index=child->char_index; +# endif + _synctex_node_set_sibling(child,sibling); + { + synctex_node_p N = child; + while (synctex_node_type(N) == synctex_node_type_ref) { + N = _synctex_tree_arg_sibling(N); + } + _synctex_data_set_tlc(sibling,N); + } + _synctex_data_set_h(sibling,_synctex_data_h_V(parent)+_synctex_data_width_V(parent)); + _synctex_data_set_v(sibling,_synctex_data_v_V(parent)); + child = sibling; + } else { + _synctex_error("Can't create box bdry record."); + } + sibling = _synctex_tree_child(parent); + _synctex_data_set_point(sibling,_synctex_data_point_V(parent)); + if (last_k && last_g && (child = synctex_node_child(parent))) { + /* Find the node preceding last_k */ + synctex_node_p next; + while ((next = __synctex_tree_sibling(child))) { + if (next == last_k) { + _synctex_data_set_tlc(last_k,child); + _synctex_data_set_tlc(last_g,child); + break; + } + child = next; + } + } + child = parent; + parent = _synctex_tree_parent(child); + if (!form) { + _synctex_handle_make_friend_tlc(x_handle); + } + x_handle = _synctex_handle_pop_child(x_handle); + _synctex_handle_set_tlc(x_handle,child,!form); + _synctex_make_hbox_contain_box(parent, _synctex_data_box_V(child)); +# if SYNCTEX_VERBOSE + synctex_node_log(child); +# endif + } + if (_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Incomplete container."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + last_k = last_g = NULL; + goto content_loop; + } + } else if (SYNCTEX_START_SCAN(VOID_VBOX)) { +# ifdef SYNCTEX_NOTHING +# pragma mark + SCAN VOID VBOX +# endif + ns = _synctex_parse_new_void_vbox(scanner); + if (ns.status == SYNCTEX_STATUS_OK) { + if (child) { + _synctex_node_set_sibling(child,ns.node); + } else { + _synctex_node_set_child(parent,ns.node); + } + child = ns.node; + _synctex_handle_set_tlc(x_handle, child,!form); +# if SYNCTEX_VERBOSE + synctex_node_log(child); +# endif + input.node = _synctex_input_register_line(input.node,child); + last_k = last_g = NULL; + goto content_loop; + } + } else if (SYNCTEX_START_SCAN(VOID_HBOX)) { +# ifdef SYNCTEX_NOTHING +# pragma mark + SCAN VOID HBOX +# endif + ns = _synctex_parse_new_void_hbox(scanner); + if (ns.status == SYNCTEX_STATUS_OK) { + if (_synctex_data_width(ns.node)<0) { + printf("Negative width\n"); + } + if (child) { + _synctex_node_set_sibling(child,ns.node); + } else { + _synctex_node_set_child(parent,ns.node); + } + child = ns.node; + _synctex_handle_set_tlc(x_handle, child,!form); + _synctex_make_hbox_contain_box(parent,_synctex_data_box(child)); +# if SYNCTEX_VERBOSE + synctex_node_log(child); +# endif + input.node = _synctex_input_register_line(input.node,child); + last_k = last_g = NULL; + goto content_loop; + } + } else if (SYNCTEX_START_SCAN(KERN)) { +# ifdef SYNCTEX_NOTHING +# pragma mark + SCAN KERN +# endif + ns = _synctex_parse_new_kern(scanner); + continue_scan: + if (ns.status == SYNCTEX_STATUS_OK) { + if (child) { + _synctex_node_set_sibling(child,ns.node); + } else { + _synctex_node_set_child(parent,ns.node); + } + child = ns.node; + if (!form) { + __synctex_node_make_friend_tlc(child); + } + _synctex_handle_set_tlc(x_handle, child,!form); + _synctex_make_hbox_contain_box(parent,_synctex_data_xob(child)); +# if SYNCTEX_VERBOSE + synctex_node_log(child); +# endif + input.node = _synctex_input_register_line(input.node,child); + last_k = child; + last_g = NULL; + goto content_loop; + } + } else if (SYNCTEX_START_SCAN(GLUE)) { +# ifdef SYNCTEX_NOTHING +# pragma mark + SCAN GLUE +# endif + ns = _synctex_parse_new_glue(scanner); + if (ns.status == SYNCTEX_STATUS_OK) { + if (child) { + _synctex_node_set_sibling(child,ns.node); + } else { + _synctex_node_set_child(parent,ns.node); + } + child = ns.node; + if (!form) { + __synctex_node_make_friend_tlc(child); + } + _synctex_handle_set_tlc(x_handle, child,!form); + _synctex_make_hbox_contain_point(parent,_synctex_data_point(child)); +# if SYNCTEX_VERBOSE + synctex_node_log(child); +# endif + input.node = _synctex_input_register_line(input.node,child); + if (last_k) { + last_g = child; + } else { + last_k = last_g = NULL; + } + goto content_loop; + } + } else if (SYNCTEX_START_SCAN(RULE)) { +# ifdef SYNCTEX_NOTHING +# pragma mark + SCAN RULE +# endif + ns = _synctex_parse_new_rule(scanner); + if (ns.status == SYNCTEX_STATUS_OK) { + if (child) { + _synctex_node_set_sibling(child,ns.node); + } else { + _synctex_node_set_child(parent,ns.node); + } + child = ns.node; + if (!form) { + __synctex_node_make_friend_tlc(child); + } + _synctex_handle_set_tlc(x_handle, child,!form); + /* Rules are sometimes far too big +_synctex_make_hbox_contain_box(parent,_synctex_data_box(child)); + */ +# if SYNCTEX_VERBOSE + synctex_node_log(child); +# endif + input.node = _synctex_input_register_line(input.node,child); + last_k = last_g = NULL; + goto content_loop; + } + } else if (SYNCTEX_START_SCAN(MATH)) { +# ifdef SYNCTEX_NOTHING +# pragma mark + SCAN MATH +# endif + ns = _synctex_parse_new_math(scanner); + if (ns.status == SYNCTEX_STATUS_OK) { + if (child) { + _synctex_node_set_sibling(child,ns.node); + } else { + _synctex_node_set_child(parent,ns.node); + } + child = ns.node; + if (!form) { + __synctex_node_make_friend_tlc(child); + } + _synctex_handle_set_tlc(x_handle, child,!form); + _synctex_make_hbox_contain_point(parent,_synctex_data_point(child)); +# if SYNCTEX_VERBOSE + synctex_node_log(child); +# endif + input.node = _synctex_input_register_line(input.node,child); + last_k = last_g = NULL; + goto content_loop; + } + } else if (SYNCTEX_START_SCAN(FORM_REF)) { +# ifdef SYNCTEX_NOTHING +# pragma mark + SCAN FORM REF +# endif +#if SYNCTEX_DEBUG>500 + synctex_node_display(parent); + synctex_node_display(child); +#endif + ns = _synctex_parse_new_ref(scanner); + if (ns.status == SYNCTEX_STATUS_OK) { + if (child) { + _synctex_node_set_sibling(child,ns.node); + } else { + _synctex_node_set_child(parent,ns.node); + } + child = ns.node; + if (form) { + if (scanner->ref_in_form) { + synctex_tree_set_friend(child,scanner->ref_in_form); + } + scanner->ref_in_form = child; + } else { + if (scanner->ref_in_sheet) { + synctex_tree_set_friend(child,scanner->ref_in_sheet); + } + scanner->ref_in_sheet = child; + } +# if SYNCTEX_VERBOSE + synctex_node_log(child); +# endif + last_k = last_g = NULL; + goto content_loop; + } + } else if (SYNCTEX_START_SCAN(BOUNDARY)) { +# ifdef SYNCTEX_NOTHING +# pragma mark + SCAN BOUNDARY +# endif + ns = _synctex_parse_new_boundary(scanner); + if (ns.status == SYNCTEX_STATUS_OK) { + if (child) { + _synctex_node_set_sibling(child,ns.node); + } else { + _synctex_node_set_child(parent,ns.node); + } + if (synctex_node_type(child)==synctex_node_type_box_bdry + || _synctex_tree_target(x_handle)) { + child = _synctex_tree_reset_child(x_handle); + child = _synctex_new_handle_with_child(child); + __synctex_tree_set_sibling(child, x_handle); + x_handle = child; + _synctex_tree_set_target(x_handle,ns.node); + } else if (!form) { + __synctex_node_make_friend_tlc(ns.node); + } + child = ns.node; + _synctex_make_hbox_contain_point(parent,_synctex_data_point(child)); +# if SYNCTEX_VERBOSE + synctex_node_log(child); +# endif + input.node = _synctex_input_register_line(input.node,child); + last_k = last_g = NULL; + goto content_loop; + } + } else if (SYNCTEX_START_SCAN(CHARACTER)) { +# ifdef SYNCTEX_NOTHING +# pragma mark + SCAN CHARACTER +# endif + ++SYNCTEX_CUR; + if (_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Missing end of container."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + last_k = last_g = NULL; + goto content_loop; + } else if (SYNCTEX_START_SCAN(ANCHOR)) { +# ifdef SYNCTEX_NOTHING +# pragma mark + SCAN ANCHOR +# endif + goto scan_anchor; + } else if (SYNCTEX_START_SCAN(END_SHEET)) { + if (sheet && parent == sheet) { +# ifdef SYNCTEX_NOTHING +# pragma mark + SCAN TEEHS +# endif + ++SYNCTEX_CUR; + if (_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Missing anchor."); + } + parent = sheet = NULL; + goto main_loop; + } + } else if (SYNCTEX_START_SCAN(END_FORM)) { + if (parent == form && form_depth > 0) { +# ifdef SYNCTEX_NOTHING +# pragma mark + SCAN MROF +# endif + ++SYNCTEX_CUR; + --form_depth; + if (_synctex_next_line(scanner)<SYNCTEX_STATUS_OK + && (form_depth || sheet)) { + _synctex_error("Missing end of container."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + if ((parent = _synctex_tree_parent(form))) { + _synctex_tree_reset_parent(form); + child = form; + form = parent; + goto content_loop; + } else if (sheet) { + form = NULL; + parent = sheet; + child = synctex_node_last_sibling(child); + goto content_loop; + } + goto main_loop; + } + } + _synctex_error("Ignored record <%.20s...>(line %i)\n",SYNCTEX_CUR, scanner->reader->line_number+1); + if (_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Missing end of sheet/form."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + last_k = last_g = NULL; + goto content_loop; + } + zs = _synctex_buffer_get_available_size(scanner,1); + if (zs.size == 0){ + _synctex_error("Incomplete synctex file, postamble missing."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + last_k = last_g = NULL; + goto content_loop; +} +#undef SYNCTEX_RETURN +/** + * Replace ref in its tree hierarchy by a single box + * proxy to the contents of the associated form. + * - argument ref: a ref node with no friend + * - return the proxy created. + * - note: Does nothing if ref is not owned. + * - note: On return, ref will have no parent nor sibling. + * The caller is responsible for releasing ref. + * - note: this is where root proxies are created. + * - note: the target of the root proxy is the content + * of a form. + */ +SYNCTEX_INLINE static synctex_ns_s __synctex_replace_ref(synctex_node_p ref) { + synctex_ns_s ns = {NULL,SYNCTEX_STATUS_OK}; + synctex_node_p parent; + if ((parent = _synctex_tree_parent(ref))) { + synctex_node_p sibling = __synctex_tree_reset_sibling(ref); + synctex_node_p arg_sibling = synctex_node_arg_sibling(ref); + /* arg_sibling != NULL because the child of a box + * is always a box boundary, not a ref. */ + synctex_node_p target = synctex_form_content(ref->class_->scanner, _synctex_data_tag(ref)); + /* The target is a single node (box) + * with children and no siblings. */ + if ((ns.node = __synctex_new_proxy_from_ref_to(ref, target))) { + /* Insert this proxy instead of ref. */ + _synctex_node_set_sibling(arg_sibling,ns.node); + /* Then append the original sibling of ref. */ + _synctex_node_set_sibling(ns.node,sibling); +# if defined(SYNCTEX_USE_CHARINDEX) + if (synctex_node_type(sibling) == synctex_node_type_box_bdry) { + /* The sibling is the last box boundary + * which may have a less accurate information */ + sibling->char_index = arg_sibling->char_index; + sibling->line_index = arg_sibling->line_index; + } +#endif +#if SYNCTEX_DEBUG>500 + printf("! Ref replacement:\n"); + synctex_node_log(ref); + synctex_node_display(synctex_node_sibling(ref)); +#endif + } else /* simply remove ref */ { + _synctex_tree_set_sibling(arg_sibling,sibling); + } + __synctex_tree_reset_parent(ref); + } else { + _synctex_error("! Missing parent in __synctex_replace_ref. " + "Please report."); + ns.status = SYNCTEX_STATUS_BAD_ARGUMENT; + } + return ns; +} +/** + * - argument ref: is the starting point of a linked list + * of refs. The link is made through the friend field. + * - returns: the status and the list of all the proxies + * created. The link is made through the friend field. + * - note: All refs are freed + */ +SYNCTEX_INLINE static synctex_ns_s _synctex_post_process_ref(synctex_node_p ref) { + synctex_ns_s ns = {NULL, SYNCTEX_STATUS_OK}; + while (ref) { + synctex_node_p next_ref = _synctex_tree_reset_friend(ref); + synctex_ns_s sub_ns = __synctex_replace_ref(ref); + if (sub_ns.status < ns.status) { + ns.status = sub_ns.status; + } else { + /* Insert all the created proxies in the list + * sub_ns.node is the last friend, + */ + synctex_tree_set_friend(sub_ns.node,ns.node); + ns.node = sub_ns.node; + } + synctex_node_free(ref); + ref = next_ref; + } + return ns; +} +typedef synctex_node_p (* synctex_processor_f)(synctex_node_p node); +/** + * Apply the processor f to the tree hierarchy rooted at proxy. + * proxy has replaced a form ref, no children yet. + * As a side effect all the hierarchy of nodes will be created. + */ +SYNCTEX_INLINE static synctex_status_t _synctex_post_process_proxy(synctex_node_p proxy, synctex_processor_f f) { + while(proxy) { + synctex_node_p next_proxy = _synctex_tree_friend(proxy); + synctex_node_p halt = __synctex_tree_sibling(proxy); + /* if proxy is the last sibling, halt is NULL. + * Find what should be a next node, + * without creating new nodes. */ + if (!halt) { + synctex_node_p parent = _synctex_tree_parent(proxy); + halt = __synctex_tree_sibling(parent); + while (!halt && parent) { + parent = _synctex_tree_parent(parent); + halt = __synctex_tree_sibling(parent); + } + } + do { +#if SYNCTEX_DEBUG>500 + printf("POST PROCESSING %s\n",_synctex_node_abstract(proxy)); + { + int i,j = 0; + for (i=0;i<proxy->class_->scanner->number_of_lists;++i) { + synctex_node_p N = proxy->class_->scanner->lists_of_friends[i]; + do { + if (N==proxy) { + ++j; + printf("%s",_synctex_node_abstract(N)); + } + } while ((N = _synctex_tree_friend(N))); + } + if (j) { + printf("\nBeforehand %i match\n",j); + } + } +#endif + f(proxy); +#if SYNCTEX_DEBUG>500 + { + int i,j = 0; + for (i=0;i<proxy->class_->scanner->number_of_lists;++i) { + synctex_node_p N = proxy->class_->scanner->lists_of_friends[i]; + do { + if (N==proxy) { + ++j; + printf("%s",_synctex_node_abstract(N)); + } + } while ((N = _synctex_tree_friend(N))); + } + if (j) { + printf("\n%i match\n",j); + } + } +#endif + /* Side effect: create the hierarchy on the fly */ + proxy = synctex_node_next(proxy); /* Change is here */ +#if SYNCTEX_DEBUG>500 + if (proxy) { + int i,j = 0; + for (i=0;i<proxy->class_->scanner->number_of_lists;++i) { + synctex_node_p N = proxy->class_->scanner->lists_of_friends[i]; + do { + if (N==proxy) { + ++j; + printf("%s",_synctex_node_abstract(N)); + } + } while ((N = _synctex_tree_friend(N))); + } + if (j) { + printf("\nnext %i match\n",j); + } + } +#endif + } while (proxy && proxy != halt); + proxy = next_proxy; + } + return SYNCTEX_STATUS_OK; +} +/** + * Replace all the form refs by root box proxies. + * Create the node hierarchy and update the friends. + * On entry, the refs are collected as a friend list + * in either a form or a sheet + * - parameter: the owning scanner + */ +SYNCTEX_INLINE static synctex_status_t _synctex_post_process(synctex_scanner_p scanner) { + synctex_status_t status = SYNCTEX_STATUS_OK; + synctex_ns_s ns = {NULL,SYNCTEX_STATUS_NOT_OK}; +#if SYNCTEX_DEBUG>500 + printf("! entering _synctex_post_process.\n"); + synctex_node_display(scanner->sheet); + synctex_node_display(scanner->form); +#endif + /* replace form refs inside forms by box proxies */ + ns = _synctex_post_process_ref(scanner->ref_in_form); + scanner->ref_in_form = NULL;/* it was just released */ + if (ns.status<status) { + status = ns.status; + } +#if SYNCTEX_DEBUG>500 + printf("! ref replaced in form _synctex_post_process.\n"); + synctex_node_display(scanner->form); +#endif + /* Create all the form proxy nodes on the fly. + * ns.node is the root of the list of + * newly created proxies. + * There might be a problem with cascading proxies. + * In order to be properly managed, the data must + * be organized in the right way. + * The inserted form must be defined before + * the inserting one. *TeX will take care of that. */ + ns.status = _synctex_post_process_proxy(ns.node,&_synctex_tree_reset_friend); + if (ns.status<status) { + status = ns.status; + } + /* replace form refs inside sheets by box proxies */ + ns = _synctex_post_process_ref(scanner->ref_in_sheet); + if (ns.status<status) { + status = ns.status; + } + scanner->ref_in_sheet = NULL; +#if SYNCTEX_DEBUG>500 + printf("! ref replaced in sheet _synctex_post_process.\n"); + synctex_node_display(scanner->sheet); +#endif +#if 0 + { + int i; + for (i=0;i<scanner->number_of_lists;++i) { + synctex_node_p P = ns.node; + do { + synctex_node_p N = scanner->lists_of_friends[i]; + do { + if (P == N) { + printf("Already registered.\n"); + synctex_node_display(N); + break; + } + } while ((N = _synctex_tree_friend(N))); + } while((P = _synctex_tree_friend(P))); + } + } +#endif +#if SYNCTEX_DEBUG>10000 + { + int i; + for (i=0;i<scanner->number_of_lists;++i) { + synctex_node_p P = scanner->lists_of_friends[i]; + int j = 0; + while (P) { + ++j; + synctex_node_log(P); + P = _synctex_tree_friend(P); + } + if (j) { + printf("friends %i -> # %i\n",i,j); + } + } + } +#endif + ns.status = _synctex_post_process_proxy(ns.node,&__synctex_proxy_make_friend_and_next_hbox); + if (ns.status<status) { + status = ns.status; + } +#if SYNCTEX_DEBUG>500 + printf("! exiting _synctex_post_process.\n"); + synctex_node_display(scanner->sheet); + synctex_node_display(scanner->form); + printf("! display all.\n"); + synctex_node_display(scanner->sheet); + synctex_node_display(scanner->form); +#endif + return status; +} +/* Used when parsing the synctex file + */ +static synctex_status_t _synctex_scan_content(synctex_scanner_p scanner) { + scanner->reader->lastv = -1; + synctex_status_t status = 0; + if (NULL == scanner) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + /* Find where this section starts */ +content_not_found: + status = _synctex_match_string(scanner,"Content:"); + if (status<SYNCTEX_STATUS_EOF) { + return status; + } + if (_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) { + _synctex_error("Incomplete Content."); + return SYNCTEX_STATUS_ERROR; + } + if (status == SYNCTEX_STATUS_NOT_OK) { + goto content_not_found; + } + status = __synctex_parse_sfi(scanner); + if (status == SYNCTEX_STATUS_OK) { + status = _synctex_post_process(scanner); + } + return status; +} +synctex_scanner_p synctex_scanner_new() { + synctex_scanner_p scanner =(synctex_scanner_p)_synctex_malloc(sizeof(synctex_scanner_s)); + if (scanner) { + if (!(scanner->reader = _synctex_malloc(sizeof(synctex_reader_s)))) { + _synctex_free(scanner); + return NULL; + } +# ifdef SYNCTEX_NOTHING +# pragma mark - +# endif +# define DEFINE_synctex_scanner_class(NAME)\ + scanner->class_[synctex_node_type_##NAME] = synctex_class_##NAME;\ +(scanner->class_[synctex_node_type_##NAME]).scanner = scanner + DEFINE_synctex_scanner_class(input); + DEFINE_synctex_scanner_class(sheet); + DEFINE_synctex_scanner_class(form); + DEFINE_synctex_scanner_class(hbox); + DEFINE_synctex_scanner_class(void_hbox); + DEFINE_synctex_scanner_class(vbox); + DEFINE_synctex_scanner_class(void_vbox); + DEFINE_synctex_scanner_class(kern); + DEFINE_synctex_scanner_class(glue); + DEFINE_synctex_scanner_class(rule); + DEFINE_synctex_scanner_class(math); + DEFINE_synctex_scanner_class(boundary); + DEFINE_synctex_scanner_class(box_bdry); + DEFINE_synctex_scanner_class(ref); + DEFINE_synctex_scanner_class(proxy_hbox); + DEFINE_synctex_scanner_class(proxy_vbox); + DEFINE_synctex_scanner_class(proxy); + DEFINE_synctex_scanner_class(proxy_last); + DEFINE_synctex_scanner_class(handle); + /* set up the lists of friends */ + scanner->number_of_lists = 1024; + scanner->lists_of_friends = (synctex_node_r)_synctex_malloc(scanner->number_of_lists*sizeof(synctex_node_p)); + if (NULL == scanner->lists_of_friends) { + synctex_scanner_free(scanner); + _synctex_error("malloc:2"); + return NULL; + } + scanner->display_switcher = 100; + scanner->display_prompt = (char *)_synctex_display_prompt+strlen(_synctex_display_prompt)-1; + } + return scanner; +} +/* Where the synctex scanner is created. */ +synctex_scanner_p synctex_scanner_new_with_output_file(const char * output, const char * build_directory, int parse) { + synctex_scanner_p scanner = synctex_scanner_new(); + if (NULL == scanner) { + _synctex_error("malloc problem"); + return NULL; + } + if ((scanner->reader = synctex_reader_init_with_output_file(scanner->reader, output, build_directory))) { + return parse? synctex_scanner_parse(scanner):scanner; + } + _synctex_error("No file?"); + return NULL; +} + +/* The scanner destructor + */ +int synctex_scanner_free(synctex_scanner_p scanner) { + int node_count = 0; + if (scanner) { + if (SYNCTEX_FILE) { + gzclose(SYNCTEX_FILE); + SYNCTEX_FILE = NULL; + } + synctex_node_free(scanner->sheet); + synctex_node_free(scanner->form); + synctex_node_free(scanner->input); + synctex_reader_free(scanner->reader); + SYNCTEX_SCANNER_FREE_HANDLE(scanner); + synctex_iterator_free(scanner->iterator); + free(scanner->output_fmt); + free(scanner->lists_of_friends); +#if SYNCTEX_USE_NODE_COUNT>0 + node_count = scanner->node_count; +#endif + free(scanner); + } + return node_count; +} + +/* Where the synctex scanner parses the contents of the file. */ +synctex_scanner_p synctex_scanner_parse(synctex_scanner_p scanner) { + synctex_status_t status = 0; + if (!scanner || scanner->flags.has_parsed) { + return scanner; + } + scanner->flags.has_parsed=1; + scanner->pre_magnification = 1000; + scanner->pre_unit = 8192; + scanner->pre_x_offset = scanner->pre_y_offset = 578; + /* initialize the offset with a fake unprobable value, + * If there is a post scriptum section, this value will be overridden by the real life value */ + scanner->x_offset = scanner->y_offset = 6.027e23f; + scanner->reader->line_number = 1; + + SYNCTEX_START = (char *)malloc(SYNCTEX_BUFFER_SIZE+1); /* one more character for null termination */ + if (NULL == SYNCTEX_START) { + _synctex_error("! malloc error in synctex_scanner_parse."); + bailey: +#ifdef SYNCTEX_DEBUG + return scanner; +#else + synctex_scanner_free(scanner); + return NULL; +#endif + } + synctex_scanner_set_display_switcher(scanner, 1000); + SYNCTEX_END = SYNCTEX_START+SYNCTEX_BUFFER_SIZE; + /* SYNCTEX_END always points to a null terminating character. + * Maybe there is another null terminating character between SYNCTEX_CUR and SYNCTEX_END-1. + * At least, we are sure that SYNCTEX_CUR points to a string covering a valid part of the memory. */ + *SYNCTEX_END = '\0'; + SYNCTEX_CUR = SYNCTEX_END; +# if defined(SYNCTEX_USE_CHARINDEX) + scanner->reader->charindex_offset = -SYNCTEX_BUFFER_SIZE; +# endif + status = _synctex_scan_preamble(scanner); + if (status<SYNCTEX_STATUS_OK) { + _synctex_error("Bad preamble\n"); + goto bailey; + } + status = _synctex_scan_content(scanner); + if (status<SYNCTEX_STATUS_OK) { + _synctex_error("Bad content\n"); + goto bailey; + } + status = _synctex_scan_postamble(scanner); + if (status<SYNCTEX_STATUS_OK) { + _synctex_error("Bad postamble. Ignored\n"); + } +#if SYNCTEX_DEBUG>500 + synctex_scanner_set_display_switcher(scanner, 100); + synctex_node_display(scanner->sheet); + synctex_node_display(scanner->form); +#endif + synctex_scanner_set_display_switcher(scanner, 1000); + /* Everything is finished, free the buffer, close the file */ + free((void *)SYNCTEX_START); + SYNCTEX_START = SYNCTEX_CUR = SYNCTEX_END = NULL; + gzclose(SYNCTEX_FILE); + SYNCTEX_FILE = NULL; + /* Final tuning: set the default values for various parameters */ + /* 1 pre_unit = (scanner->pre_unit)/65536 pt = (scanner->pre_unit)/65781.76 bp + * 1 pt = 65536 sp */ + if (scanner->pre_unit<=0) { + scanner->pre_unit = 8192; + } + if (scanner->pre_magnification<=0) { + scanner->pre_magnification = 1000; + } + if (scanner->unit <= 0) { + /* no post magnification */ + scanner->unit = scanner->pre_unit / 65781.76;/* 65781.76 or 65536.0*/ + } else { + /* post magnification */ + scanner->unit *= scanner->pre_unit / 65781.76; + } + scanner->unit *= scanner->pre_magnification / 1000.0; + if (scanner->x_offset > 6e23) { + /* no post offset */ + scanner->x_offset = scanner->pre_x_offset * (scanner->pre_unit / 65781.76); + scanner->y_offset = scanner->pre_y_offset * (scanner->pre_unit / 65781.76); + } else { + /* post offset */ + scanner->x_offset /= 65781.76f; + scanner->y_offset /= 65781.76f; + } + return scanner; +#undef SYNCTEX_FILE +} + +/* Scanner accessors. + */ +int synctex_scanner_pre_x_offset(synctex_scanner_p scanner){ + return scanner?scanner->pre_x_offset:0; +} +int synctex_scanner_pre_y_offset(synctex_scanner_p scanner){ + return scanner?scanner->pre_y_offset:0; +} +int synctex_scanner_x_offset(synctex_scanner_p scanner){ + return scanner?scanner->x_offset:0; +} +int synctex_scanner_y_offset(synctex_scanner_p scanner){ + return scanner?scanner->y_offset:0; +} +float synctex_scanner_magnification(synctex_scanner_p scanner){ + return scanner?scanner->unit:1; +} +void synctex_scanner_display(synctex_scanner_p scanner) { + if (NULL == scanner) { + return; + } + printf("The scanner:\noutput:%s\noutput_fmt:%s\nversion:%i\n",scanner->reader->output,scanner->output_fmt,scanner->version); + printf("pre_unit:%i\nx_offset:%i\ny_offset:%i\n",scanner->pre_unit,scanner->pre_x_offset,scanner->pre_y_offset); + printf("count:%i\npost_magnification:%f\npost_x_offset:%f\npost_y_offset:%f\n", + scanner->count,scanner->unit,scanner->x_offset,scanner->y_offset); + printf("The input:\n"); + synctex_node_display(scanner->input); + if (scanner->count<1000) { + printf("The sheets:\n"); + synctex_node_display(scanner->sheet); + printf("The friends:\n"); + if (scanner->lists_of_friends) { + int i = scanner->number_of_lists; + synctex_node_p node; + while(i--) { + printf("Friend index:%i\n",i); + node = (scanner->lists_of_friends)[i]; + while(node) { + printf("%s:%i,%i\n", + synctex_node_isa(node), + _synctex_data_tag(node), + _synctex_data_line(node) + ); + node = _synctex_tree_friend(node); + } + } + } + } else { + printf("SyncTeX Warning: Too many objects\n"); + } +} +/* Public */ +const char * synctex_scanner_get_name(synctex_scanner_p scanner,int tag) { + synctex_node_p input = NULL; + if (NULL == scanner) { + return NULL; + } + if ((input = scanner->input)) {; + do { + if (tag == _synctex_data_tag(input)) { + return (_synctex_data_name(input)); + } + } while((input = __synctex_tree_sibling(input))); + } + return NULL; +} +const char * synctex_node_get_name(synctex_node_p node) { + if (node) { + return synctex_scanner_get_name(node->class_->scanner,_synctex_data_tag(node)); + } + return NULL; +} + +static int _synctex_scanner_get_tag(synctex_scanner_p scanner,const char * name); +static int _synctex_scanner_get_tag(synctex_scanner_p scanner,const char * name) { + synctex_node_p input = NULL; + if (NULL == scanner) { + return 0; + } + if ((input = scanner->input)) { + do { + if (_synctex_is_equivalent_file_name(name,(_synctex_data_name(input)))) { + return _synctex_data_tag(input); + } + } while((input = __synctex_tree_sibling(input))); + } + // 2011 version + name = _synctex_base_name(name); + if ((input = scanner->input)) { + do { + if (_synctex_is_equivalent_file_name(name,_synctex_base_name(_synctex_data_name(input)))) { + synctex_node_p other_input = input; + while((other_input = __synctex_tree_sibling(other_input))) { + if (_synctex_is_equivalent_file_name(name,_synctex_base_name(_synctex_data_name(other_input))) + && (strlen(_synctex_data_name(input))!=strlen(_synctex_data_name(other_input)) + || strncmp(_synctex_data_name(other_input),_synctex_data_name(input),strlen(_synctex_data_name(input))))) { + // There is a second possible candidate + return 0; + } + } + return _synctex_data_tag(input); + } + } while((input = __synctex_tree_sibling(input))); + } + return 0; +} + +int synctex_scanner_get_tag(synctex_scanner_p scanner,const char * name) { + size_t char_index = strlen(name); + if ((scanner = synctex_scanner_parse(scanner)) && (0 < char_index)) { + /* the name is not void */ + char_index -= 1; + if (!SYNCTEX_IS_PATH_SEPARATOR(name[char_index])) { + /* the last character of name is not a path separator */ + int result = _synctex_scanner_get_tag(scanner,name); + if (result) { + return result; + } else { + /* the given name was not the one known by TeX + * try a name relative to the enclosing directory of the scanner->output file */ + const char * relative = name; + const char * ptr = scanner->reader->output; + while((strlen(relative) > 0) && (strlen(ptr) > 0) && (*relative == *ptr)) + { + relative += 1; + ptr += 1; + } + /* Find the last path separator before relative */ + while(relative > name) { + if (SYNCTEX_IS_PATH_SEPARATOR(*(relative-1))) { + break; + } + relative -= 1; + } + if ((relative > name) && (result = _synctex_scanner_get_tag(scanner,relative))) { + return result; + } + if (SYNCTEX_IS_PATH_SEPARATOR(name[0])) { + /* No tag found for the given absolute name, + * Try each relative path starting from the shortest one */ + while(0<char_index) { + char_index -= 1; + if (SYNCTEX_IS_PATH_SEPARATOR(name[char_index]) + && (result = _synctex_scanner_get_tag(scanner,name+char_index+1))) { + return result; + } + } + } + } + return result; + } + } + return 0; +} +synctex_node_p synctex_scanner_input(synctex_scanner_p scanner) { + return scanner?scanner->input:NULL; +} +synctex_node_p synctex_scanner_input_with_tag(synctex_scanner_p scanner, int tag) { + synctex_node_p input = scanner?scanner->input:NULL; + while (_synctex_data_tag(input)!=tag) { + if ((input = __synctex_tree_sibling(input))) { + continue; + } + break; + } + return input; +} +const char * synctex_scanner_get_output_fmt(synctex_scanner_p scanner) { + return NULL != scanner && scanner->output_fmt?scanner->output_fmt:""; +} +const char * synctex_scanner_get_output(synctex_scanner_p scanner) { + return NULL != scanner && scanner->reader->output?scanner->reader->output:""; +} +const char * synctex_scanner_get_synctex(synctex_scanner_p scanner) { + return NULL != scanner && scanner->reader->synctex?scanner->reader->synctex:""; +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Public node attributes +# endif + +# define SYNCTEX_DEFINE_NODE_HVWHD(WHAT) \ +int synctex_node_##WHAT(synctex_node_p node) { \ + return (node && node->class_->inspector->WHAT)? \ + node->class_->inspector->WHAT(node): 0; \ +} +# define SYNCTEX_DEFINE_PROXY_HV(WHAT) \ +static int _synctex_proxy_##WHAT(synctex_proxy_p proxy) { \ + synctex_node_p target = _synctex_tree_target(proxy); \ + if (target) { \ + return _synctex_data_##WHAT(proxy)+synctex_node_##WHAT(target); \ + } else { \ + return proxy? _synctex_data_##WHAT(proxy): 0; \ + } \ +} +#define SYNCTEX_DEFINE_PROXY_TLCWVD(WHAT) \ +static int _synctex_proxy_##WHAT(synctex_proxy_p proxy) { \ + synctex_node_p target = _synctex_tree_target(proxy); \ + return target? synctex_node_##WHAT(target): 0; \ +} + +/** + * The horizontal location of the node. + * Idem for v, width, height and depth. + * - parameter node: a node with geometrical information. + * - returns: an integer. + * - requires: every proxy node has a target. + * - note: recursive call if the parameter has a proxy. + * - author: JL + */ +SYNCTEX_DEFINE_NODE_HVWHD(h) +SYNCTEX_DEFINE_NODE_HVWHD(v) +SYNCTEX_DEFINE_NODE_HVWHD(width) +SYNCTEX_DEFINE_NODE_HVWHD(height) +SYNCTEX_DEFINE_NODE_HVWHD(depth) +SYNCTEX_DEFINE_PROXY_TLCWVD(tag) +SYNCTEX_DEFINE_PROXY_TLCWVD(line) +SYNCTEX_DEFINE_PROXY_TLCWVD(column) +SYNCTEX_DEFINE_PROXY_HV(h) +SYNCTEX_DEFINE_PROXY_HV(v) +SYNCTEX_DEFINE_PROXY_TLCWVD(width) +SYNCTEX_DEFINE_PROXY_TLCWVD(height) +SYNCTEX_DEFINE_PROXY_TLCWVD(depth) + +/** + * Whether the argument is a box, + * either vertical or horizontal, + * either void or not, + * or a proxy to such a box. + * - parameter NODE: of type synctex_node_p + * - returns: yorn + */ + +SYNCTEX_INLINE static synctex_bool_t _synctex_node_is_box(synctex_node_p node) { + return node && + (node->class_->type == synctex_node_type_hbox + || node->class_->type == synctex_node_type_void_hbox + || node->class_->type == synctex_node_type_vbox + || node->class_->type == synctex_node_type_void_vbox + || _synctex_node_is_box(_synctex_tree_target(node))); +} + +/** + * Whether the argument is a handle. + * Handles are similar to proxies because they have a target. + * They are used for query results. + * - parameter NODE: of type synctex_node_p + * - returns: yorn + */ + +SYNCTEX_INLINE static synctex_bool_t _synctex_node_is_handle(synctex_node_p node) { + return node && + (node->class_->type == synctex_node_type_handle); +} + +/** + * Resolves handle indirection. + * - parameter node: of type synctex_node_p + * - returns: node if it is not a handle, + * its target otherwise. + */ + +SYNCTEX_INLINE static synctex_node_p _synctex_node_or_handle_target(synctex_node_p node) { + return _synctex_node_is_handle(node)? + _synctex_tree_target(node):node; +} + +/** + * Whether the argument is an hbox. + * - parameter NODE: of type synctex_node_p + * - returns: yorn + */ + +SYNCTEX_INLINE static synctex_bool_t _synctex_node_is_hbox(synctex_node_p node) { + return node && + (node->class_->type == synctex_node_type_hbox + || node->class_->type == synctex_node_type_void_hbox + || _synctex_node_is_hbox(_synctex_tree_target(node))); +} + +/** + * The horizontal location of the first box enclosing node. + * - parameter node: a node with geometrical information. + * - returns: an integer. + * - author: JL + */ +int synctex_node_box_h(synctex_node_p node) { + if (_synctex_node_is_box(node) || (node = _synctex_tree_parent(node))) { + return synctex_node_h(node); + } + return 0; +} +/** + * The vertical location of the first box enclosing node. + * - parameter node: a node with geometrical information. + * - returns: an integer. + * - author: JL + */ +int synctex_node_box_v(synctex_node_p node) { + if (_synctex_node_is_box(node) || (node = _synctex_tree_parent(node))) { + return synctex_node_v(node); + } + return 0; +} +/** + * The width of the first box enclosing node. + * - parameter node: a node with geometrical information. + * - returns: an integer. + * - author: JL + */ +int synctex_node_box_width(synctex_node_p node) { + if (_synctex_node_is_box(node) || (node = _synctex_tree_parent(node))) { + return synctex_node_width(node); + } + return 0; +} +/** + * The height of the first box enclosing node. + * - parameter node: a node with geometrical information. + * - returns: an integer. + * - author: JL + */ +int synctex_node_box_height(synctex_node_p node) { + if (_synctex_node_is_box(node) || (node = _synctex_tree_parent(node))) { + return synctex_node_height(node); + } + return 0; +} +/** + * The depth of the first box enclosing node. + * - parameter node: a node with geometrical information. + * - returns: an integer. + * - author: JL + */ +int synctex_node_box_depth(synctex_node_p node) { + if (_synctex_node_is_box(node) || (node = _synctex_tree_parent(node))) { + return synctex_node_depth(node); + } + return 0; +} +/** + * The horizontal location of an hbox, corrected with contents. + * - parameter node: an hbox node. + * - returns: an integer, 0 if node is not an hbox or an hbox proxy. + * - note: recursive call when node is an hbox proxy. + * - author: JL + */ +int synctex_node_hbox_h(synctex_node_p node) { + switch(synctex_node_type(node)) { + case synctex_node_type_hbox: + return _synctex_data_h_V(node); + case synctex_node_type_proxy_hbox: + return _synctex_data_h(node)+synctex_node_hbox_h(_synctex_tree_target(node)); + default: + return 0; + } +} +/** + * The vertical location of an hbox, corrected with contents. + * - parameter node: an hbox node. + * - returns: an integer, 0 if node is not an hbox or an hbox proxy. + * - note: recursive call when node is an hbox proxy. + * - author: JL + */ +int synctex_node_hbox_v(synctex_node_p node) { + switch(synctex_node_type(node)) { + case synctex_node_type_hbox: + return _synctex_data_v_V(node); + case synctex_node_type_proxy_hbox: + return _synctex_data_v(node)+synctex_node_hbox_v(_synctex_tree_target(node)); + default: + return 0; + } +} +/** + * The width of an hbox, corrected with contents. + * - parameter node: an hbox node, 0 if node is not an hbox or an hbox proxy. + * - returns: an integer. + * - author: JL + */ +int synctex_node_hbox_width(synctex_node_p node) { + synctex_node_p target = _synctex_tree_target(node); + if (target) { + node = target; + } + return synctex_node_type(node) == synctex_node_type_hbox? + _synctex_data_width_V(node): 0; +} +/** + * The height of an hbox, corrected with contents. + * - parameter node: an hbox node. + * - returns: an integer, 0 if node is not an hbox or an hbox proxy. + * - author: JL + */ +int synctex_node_hbox_height(synctex_node_p node) { + synctex_node_p target = _synctex_tree_target(node); + if (target) { + node = target; + } + return synctex_node_type(node) == synctex_node_type_hbox? + _synctex_data_height_V(node): 0; +} +/** + * The depth of an hbox, corrected with contents. + * - parameter node: an hbox node. + * - returns: an integer, 0 if node is not an hbox or an hbox proxy. + * - note: recursive call when node is an hbox proxy. + * - author: JL + */ +int synctex_node_hbox_depth(synctex_node_p node) { + synctex_node_p target = _synctex_tree_target(node); + if (target) { + node = target; + } + return synctex_node_type(node) == synctex_node_type_hbox? + _synctex_data_depth_V(node): 0; +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Public node visible attributes +# endif + +#define SYNCTEX_VISIBLE_SIZE(node,s) \ +(s)*node->class_->scanner->unit +#define SYNCTEX_VISIBLE_DISTANCE_h(node,d) \ +((d)*node->class_->scanner->unit+node->class_->scanner->x_offset) +#define SYNCTEX_VISIBLE_DISTANCE_v(node,d) \ +((d)*node->class_->scanner->unit+node->class_->scanner->y_offset) +static float __synctex_node_visible_h(synctex_node_p node) { + return SYNCTEX_VISIBLE_DISTANCE_h(node,synctex_node_h(node)); +} +static float __synctex_node_visible_v(synctex_node_p node) { + return SYNCTEX_VISIBLE_DISTANCE_v(node,synctex_node_v(node)); +} +static float __synctex_node_visible_width(synctex_node_p node) { + return SYNCTEX_VISIBLE_SIZE(node,synctex_node_width(node)); +} +static float __synctex_node_visible_height(synctex_node_p node) { + return SYNCTEX_VISIBLE_SIZE(node,synctex_node_height(node)); +} +static float __synctex_node_visible_depth(synctex_node_p node) { + return SYNCTEX_VISIBLE_SIZE(node,synctex_node_depth(node)); +} +static float __synctex_proxy_visible_h(synctex_node_p node) { + return SYNCTEX_VISIBLE_DISTANCE_h(node,synctex_node_h(node)); +} +static float __synctex_proxy_visible_v(synctex_node_p node) { + return SYNCTEX_VISIBLE_DISTANCE_v(node,synctex_node_v(node)); +} +static float __synctex_proxy_visible_width(synctex_node_p node) { + synctex_node_p target = _synctex_tree_target(node); + return __synctex_node_visible_width(target); +} +static float __synctex_proxy_visible_height(synctex_node_p node) { + synctex_node_p target = _synctex_tree_target(node); + return __synctex_node_visible_height(target); +} +static float __synctex_proxy_visible_depth(synctex_node_p node) { + synctex_node_p target = _synctex_tree_target(node); + return __synctex_node_visible_depth(target); +} +static float __synctex_kern_visible_h(synctex_noxy_p noxy) { + int h = _synctex_data_h(noxy); + int width = _synctex_data_width(noxy); + return SYNCTEX_VISIBLE_DISTANCE_h(noxy, width>0?h-width:h); +} +static float __synctex_kern_visible_width(synctex_noxy_p noxy) { + int width = _synctex_data_width(noxy); + return SYNCTEX_VISIBLE_SIZE(noxy, width>0?width:-width); +} +static float __synctex_rule_visible_h(synctex_noxy_p noxy) { + int h = _synctex_data_h(noxy); + int width = _synctex_data_width(noxy); + return SYNCTEX_VISIBLE_DISTANCE_h(noxy, width>0?h:h-width); +} +static float __synctex_rule_visible_width(synctex_noxy_p noxy) { + int width = _synctex_data_width(noxy); + return SYNCTEX_VISIBLE_SIZE(noxy, width>0?width:-width); +} +static float __synctex_rule_visible_v(synctex_noxy_p noxy) { + return __synctex_node_visible_v(noxy); +} +static float __synctex_rule_visible_height(synctex_noxy_p noxy) { + return __synctex_node_visible_height(noxy); +} +static float __synctex_rule_visible_depth(synctex_noxy_p noxy) { + return __synctex_node_visible_depth(noxy); +} + +/** + * The horizontal location of node, in page coordinates. + * - parameter node: a node. + * - returns: a float. + * - author: JL + */ +float synctex_node_visible_h(synctex_node_p node){ + return node? node->class_->vispector->h(node): 0; +} +/** + * The vertical location of node, in page coordinates. + * - parameter node: a node. + * - returns: a float. + * - author: JL + */ +float synctex_node_visible_v(synctex_node_p node){ + return node? node->class_->vispector->v(node): 0; +} +/** + * The width of node, in page coordinates. + * - parameter node: a node. + * - returns: a float. + * - author: JL + */ +float synctex_node_visible_width(synctex_node_p node){ + return node? node->class_->vispector->width(node): 0; +} +/** + * The height of node, in page coordinates. + * - parameter node: a node. + * - returns: a float. + * - author: JL + */ +float synctex_node_visible_height(synctex_node_p node){ + return node? node->class_->vispector->height(node): 0; +} +/** + * The depth of node, in page coordinates. + * - parameter node: a node. + * - returns: a float. + * - author: JL + */ +float synctex_node_visible_depth(synctex_node_p node){ + return node? node->class_->vispector->depth(node): 0; +} + +/** + * The V variant of geometrical information. + * - parameter node: a node. + * - returns: an integer. + * - author: JL + */ +#define SYNCTEX_DEFINE_V(WHAT)\ +SYNCTEX_INLINE static int _synctex_node_##WHAT##_V(synctex_node_p node) { \ + synctex_node_p target = _synctex_tree_target(node); \ + if (target) { \ + return _synctex_data_##WHAT(node)+_synctex_node_##WHAT##_V(target); \ + } else if (_synctex_data_has_##WHAT##_V(node)) { \ + return _synctex_data_##WHAT##_V(node); \ + } else { \ + return _synctex_data_##WHAT(node); \ + } \ +} +SYNCTEX_DEFINE_V(h) +SYNCTEX_DEFINE_V(v) +SYNCTEX_DEFINE_V(width) +SYNCTEX_DEFINE_V(height) +SYNCTEX_DEFINE_V(depth) + +SYNCTEX_INLINE static synctex_point_s _synctex_data_point(synctex_node_p node) { + return (synctex_point_s){synctex_node_h(node),synctex_node_v(node)}; +} +SYNCTEX_INLINE static synctex_point_s _synctex_data_point_V(synctex_node_p node) { + return (synctex_point_s){_synctex_node_h_V(node),_synctex_node_v_V(node)}; +} +SYNCTEX_INLINE static synctex_point_s _synctex_data_set_point(synctex_node_p node, synctex_point_s point) { + synctex_point_s old = _synctex_data_point(node); + _synctex_data_set_h(node,point.h); + _synctex_data_set_v(node,point.v); + return old; +} +SYNCTEX_INLINE static synctex_box_s _synctex_data_box(synctex_node_p node) { + synctex_box_s box = {{0,0},{0,0}}; + int n; + n = synctex_node_width(node); + if (n<0) { + box.max.h = synctex_node_h(node); + box.min.h = box.max.h + n; + } else { + box.min.h = synctex_node_h(node); + box.max.h = box.min.h + n; + } + n = synctex_node_v(node); + box.min.v = n - synctex_node_height(node); + box.max.v = n + synctex_node_depth(node); + return box; +} +SYNCTEX_INLINE static synctex_box_s _synctex_data_xob(synctex_node_p node) { + synctex_box_s box = {{0,0},{0,0}}; + int n; + n = synctex_node_width(node); + if (n>0) { + box.max.h = synctex_node_h(node); + box.min.h = box.max.h - n; + } else { + box.min.h = synctex_node_h(node); + box.max.h = box.min.h - n; + } + n = synctex_node_v(node); + box.min.v = n - synctex_node_height(node); + box.max.v = n + synctex_node_depth(node); + return box; +} +SYNCTEX_INLINE static synctex_box_s _synctex_data_box_V(synctex_node_p node) { + synctex_box_s box = {{0,0},{0,0}}; + int n; + n = _synctex_node_width_V(node); + if (n<0) { + box.max.h = _synctex_node_h_V(node); + box.min.h = box.max.h + n; + } else { + box.min.h = _synctex_node_h_V(node); + box.max.h = box.min.h + n; + } + n = _synctex_node_v_V(node); + box.min.v = n - _synctex_node_height_V(node); + box.max.v = n + _synctex_node_depth_V(node); + return box; +} + +/** + * The higher box node in the parent hierarchy which + * mean line number is the one of node ±1. + * This enclosing box is computed as follows + * 1) get the first hbox in the parent linked list + * starting at node. + * If there is none, simply return the parent of node. + * 2) compute the mean line number + * 3) scans up the tree for the higher hbox with + * the same mean line number, ±1 eventually +* - parameter node: a node. + * - returns: a (proxy to a) box node. + * - author: JL + */ +static synctex_node_p _synctex_node_box_visible(synctex_node_p node) { + if ((node = _synctex_node_or_handle_target(node))) { + int mean = 0; + int bound = 1500000/(node->class_->scanner->pre_magnification/1000.0); + synctex_node_p parent = NULL; + /* get the first enclosing parent + * then get the highest enclosing parent with the same mean line ±1 */ + node = _synctex_node_or_handle_target(node); + if (!_synctex_node_is_box(node)) { + if ((parent = _synctex_tree_parent(node))) { + node = parent; + } else if ((node = _synctex_tree_target(node))) { + if (!_synctex_node_is_box(node)) { + if ((parent = _synctex_tree_parent(node))) { + node = parent; + } else { + return NULL; + } + } + } + } + parent = node; + mean = synctex_node_mean_line(node); + while ((parent = _synctex_tree_parent(parent))) { + if (_synctex_node_is_hbox(parent)) { + if (_synctex_abs(mean-synctex_node_mean_line(parent))>1) { + return node; + } else if (synctex_node_width(parent)>bound) { + return parent; + } else if (synctex_node_height(parent)+synctex_node_depth(parent)>bound) { + return parent; + } + node = parent; + } + } + } + return node; +} +/** + * The horizontal location of the first box enclosing node, in page coordinates. + * - parameter node: a node. + * - returns: a float. + * - author: JL + */ +float synctex_node_box_visible_h(synctex_node_p node) { + return SYNCTEX_VISIBLE_DISTANCE_h(node,_synctex_node_h_V(_synctex_node_box_visible(node))); +} +/** + * The vertical location of the first box enclosing node, in page coordinates. + * - parameter node: a node. + * - returns: a float. + * - author: JL + */ +float synctex_node_box_visible_v(synctex_node_p node) { + return SYNCTEX_VISIBLE_DISTANCE_v(node,_synctex_node_v_V(_synctex_node_box_visible(node))); +} +/** + * The width of the first box enclosing node, in page coordinates. + * - parameter node: a node. + * - returns: a float. + * - author: JL + */ +float synctex_node_box_visible_width(synctex_node_p node) { + return SYNCTEX_VISIBLE_SIZE(node,_synctex_node_width_V(_synctex_node_box_visible(node))); +} +/** + * The height of the first box enclosing node, in page coordinates. + * - parameter node: a node. + * - returns: a float. + * - author: JL + */ +float synctex_node_box_visible_height(synctex_node_p node) { + return SYNCTEX_VISIBLE_SIZE(node,_synctex_node_height_V(_synctex_node_box_visible(node))); +} +/** + * The depth of the first box enclosing node, in page coordinates. + * - parameter node: a node. + * - returns: a float. + * - author: JL + */ +float synctex_node_box_visible_depth(synctex_node_p node) { + return SYNCTEX_VISIBLE_SIZE(node,_synctex_node_depth_V(_synctex_node_box_visible(node))); +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Other public node attributes +# endif + +/** + * The page number of the sheet enclosing node. + * - parameter node: a node. + * - returns: the page number or -1 if node does not belong to a sheet tree. + * - note: a proxy target does not belong to a sheet + * but a form, its page number is always -1. + * - note: a handles does not belong to a sheet not a form. + * its page number is -1. + * - author: JL + */ +int synctex_node_page(synctex_node_p node){ + synctex_node_p parent = NULL; + while((parent = _synctex_tree_parent(node))) { + node = parent; + } + if (synctex_node_type(node) == synctex_node_type_sheet) { + return _synctex_data_page(node); + } + return -1; +} +/** + * The page number of the target. + * - author: JL + */ +SYNCTEX_INLINE static int _synctex_node_target_page(synctex_node_p node){ + return synctex_node_page(_synctex_tree_target(node)); +} + +#if defined (SYNCTEX_USE_CHARINDEX) +synctex_charindex_t synctex_node_charindex(synctex_node_p node) { + synctex_node_p target = _synctex_tree_target(node); + return target? SYNCTEX_CHARINDEX(target):(node?SYNCTEX_CHARINDEX(node):0); +} +#endif + +/** + * The tag of the node. + * - parameter node: a node. + * - returns: the tag or -1 if node is NULL. + * - author: JL + */ +int synctex_node_tag(synctex_node_p node) { + return node? node->class_->tlcpector->tag(node): -1; +} +/** + * The line of the node. + * - parameter node: a node. + * - returns: the line or -1 if node is NULL. + * - author: JL + */ +int synctex_node_line(synctex_node_p node) { + return node? node->class_->tlcpector->line(node): -1; +} +/** + * The column of the node. + * - parameter node: a node. + * - returns: the column or -1 if node is NULL. + * - author: JL + */ +int synctex_node_column(synctex_node_p node) { + return node? node->class_->tlcpector->column(node): -1; +} +/** + * The mean line number of the node. + * - parameter node: a node. + * - returns: the mean line or -1 if node is NULL. + * - author: JL + */ +int synctex_node_mean_line(synctex_node_p node) { + synctex_node_p other = _synctex_tree_target(node); + if (other) { + node = other; + } + if (_synctex_data_has_mean_line(node)) { + return _synctex_data_mean_line(node); + } + if ((other = synctex_node_parent(node))) { + if (_synctex_data_has_mean_line(other)) { + return _synctex_data_mean_line(other); + } + } + return synctex_node_line(node); +} +/** + * The weight of the node. + * - parameter node: a node. + * - returns: the weight or -1 if node is NULL. + * - author: JL + */ +int synctex_node_weight(synctex_node_p node) { + synctex_node_p target = _synctex_tree_target(node); + if (target) { + node = target; + } + return node?(synctex_node_type(node)==synctex_node_type_hbox?_synctex_data_weight(node):0):-1; +} +/** + * The number of children of the node. + * - parameter node: a node. + * - returns: the count or -1 if node is NULL. + * - author: JL + */ +int synctex_node_child_count(synctex_node_p node) { + synctex_node_p target = _synctex_tree_target(node); + if (target) { + node = target; + } + return node?(synctex_node_type(node)==synctex_node_type_hbox?_synctex_data_weight(node):0):-1; +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Sheet & Form +# endif + +/** + * The sheet of the scanner with a given page number. + * - parameter scanner: a scanner. + * - parameter page: a 1 based page number. + * If page == 0, returns the first sheet. + * - returns: a sheet or NULL. + * - author: JL + */ +synctex_node_p synctex_sheet(synctex_scanner_p scanner,int page) { + if (scanner) { + synctex_node_p sheet = scanner->sheet; + while(sheet) { + if (page == _synctex_data_page(sheet)) { + return sheet; + } + sheet = __synctex_tree_sibling(sheet); + } + if (page == 0) { + return scanner->sheet; + } + } + return NULL; +} +/** + * The form of the scanner with a given tag. + * - parameter scanner: a scanner. + * - parameter tag: an integer identifier. + * If tag == 0, returns the first form. + * - returns: a form. + * - author: JL + */ +synctex_node_p synctex_form(synctex_scanner_p scanner,int tag) { + if (scanner) { + synctex_node_p form = scanner->form; + while(form) { + if (tag == _synctex_data_tag(form)) { + return form; + } + form = __synctex_tree_sibling(form); + } + if (tag == 0) { + return scanner->form; + } + } + return NULL; +} + +/** + * The content of the sheet with given page number. + * - parameter scanner: a scanner. + * - parameter page: a 1 based page number. + * - returns: a (vertical) box node. + * - author: JL + */ +synctex_node_p synctex_sheet_content(synctex_scanner_p scanner,int page) { + if (scanner) { + return _synctex_tree_child(synctex_sheet(scanner,page)); + } + return NULL; +} + +/** + * The content of the sheet with given page number. + * - parameter scanner: a scanner. + * - parameter tag: an integer identifier. + * - returns: a box node. + * - author: JL + */ +synctex_node_p synctex_form_content(synctex_scanner_p scanner,int tag) { + if (scanner) { + return _synctex_tree_child(synctex_form(scanner,tag)); + } + return NULL; +} + +SYNCTEX_INLINE static synctex_node_p _synctex_scanner_friend(synctex_scanner_p scanner,int i) { + if (i>=0) { + i = _synctex_abs(i)%(scanner->number_of_lists); + return (scanner->lists_of_friends)[i]; + } + return NULL; +} +SYNCTEX_INLINE static synctex_bool_t _synctex_nodes_are_friend(synctex_node_p left, synctex_node_p right) { + return synctex_node_tag(left) == synctex_node_tag(right) && synctex_node_line(left) == synctex_node_line(right); +} +/** + * The sibling argument is a parent/child list of nodes of the same page. + */ +typedef struct { + int count; + synctex_node_p node; +} synctex_counted_node_s; + +SYNCTEX_INLINE static synctex_counted_node_s _synctex_vertically_sorted_v2(synctex_node_p sibling) { + /* Clean the weights of the parents */ + synctex_counted_node_s result = {0, NULL}; + synctex_node_p h = NULL; + synctex_node_p next_h = NULL; + synctex_node_p parent = NULL; + int weight = 0; + synctex_node_p N = NULL; + h = sibling; + do { + N = _synctex_tree_target(h); + parent = _synctex_tree_parent(N); + _synctex_data_set_weight(parent, 0); + } while((h = _synctex_tree_child(h))); + /* Compute the weights of the nodes */ + h = sibling; + do { + N = _synctex_tree_target(h); + parent = _synctex_tree_parent(N); + weight = _synctex_data_weight(parent); + if (weight==0) { + N = _synctex_tree_child(parent); + do { + if (_synctex_nodes_are_friend(N,sibling)) { + ++ weight; + } + } while ((N = __synctex_tree_sibling(N))); + _synctex_data_set_weight(h,weight); + _synctex_data_set_weight(parent,weight); + } + } while((h = _synctex_tree_child(h))); + /* Order handle nodes according to the weight */ + h = _synctex_tree_reset_child(sibling); + result.node = sibling; + weight = 0; + while((h)) { + N = result.node; + if (_synctex_data_weight(h)>_synctex_data_weight(N)) { + next_h = _synctex_tree_set_child(h,N); + result.node = h; + } else if (_synctex_data_weight(h) == 0) { + ++ weight; + next_h = _synctex_tree_reset_child(h); + synctex_node_free(h); + } else { + synctex_node_p next_N = NULL; + while((next_N = _synctex_tree_child(N))) { + N = next_N; + if (_synctex_data_weight(h)<_synctex_data_weight(next_N)) { + continue; + } + break; + } + next_h = _synctex_tree_set_child(h,_synctex_tree_set_child(N,h)); + } + h = next_h; + }; + h = result.node; + weight = 0; + do { + ++weight; + } while((h = _synctex_tree_child(h))); + result.count = 1; + h = result.node; + while((next_h = _synctex_tree_child(h))) { + if (_synctex_data_weight(next_h)==0) { + _synctex_tree_reset_child(h); + weight = 1; + h = next_h; + while((h = _synctex_tree_child(h))) { + ++weight; + } + synctex_node_free(next_h); + break; + } + ++result.count; + h = next_h; + } + return result; +} + +SYNCTEX_INLINE static synctex_bool_t _synctex_point_in_box_v2(synctex_point_p hitP, synctex_node_p node); + +/* This struct records distances, the left one is non negative and the right one is non positive. + * When comparing the locations of 2 different graphical objects on the page, we will have to also record the + * horizontal distance as signed to keep track of the typesetting order.*/ + +typedef struct { + synctex_node_p node; + int distance; +} synctex_nd_s; + +#define SYNCTEX_ND_0 (synctex_nd_s){NULL,INT_MAX} + +typedef synctex_nd_s * synctex_nd_p; + +typedef struct { + synctex_nd_s l; + synctex_nd_s r; +} synctex_nd_lr_s; + +/* The best container is the deeper box that contains the hit point (H,V). + * _synctex_eq_deepest_container_v2 starts with node whereas + * _synctex_box_child_deepest starts with node's children, if any + * if node is not a box, or a void box, NULL is returned. + * We traverse the node tree in a deep first manner and stop as soon as a result is found. */ +static synctex_node_p _synctex_eq_deepest_container_v2(synctex_point_p hitP, synctex_node_p node); + +SYNCTEX_INLINE static synctex_nd_lr_s _synctex_eq_get_closest_children_in_box_v2(synctex_point_p hitP, synctex_node_p node); + +/* Closest child, recursive. */ +static synctex_nd_s __synctex_closest_deep_child_v2(synctex_point_p hitP, synctex_node_p node); + +/* The smallest container between two has the smallest width or height. + * This comparison is used when there are 2 overlapping boxes that contain the hit point. + * For ConTeXt, the problem appears at each page. + * The chosen box is the one with the smallest height, then the smallest width. */ +SYNCTEX_INLINE static synctex_node_p _synctex_smallest_container_v2(synctex_node_p node, synctex_node_p other_node); + +/* Returns the distance between the hit point hit point=(H,V) and the given node. */ + +static int _synctex_point_node_distance_v2(synctex_point_p hitP, synctex_node_p node); + +/* The closest container is the box that is the one closest to the given point. + * The "visible" version takes into account the visible dimensions instead of the real ones given by TeX. */ +static synctex_nd_s _synctex_eq_closest_child_v2(synctex_point_p hitP, synctex_node_p node); + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Queries +# endif + +/** + * iterator for a deep first tree traversal. + */ +struct synctex_iterator_t { + synctex_node_p seed; + synctex_node_p top; + synctex_node_p next; + int count0; + int count; +}; + +SYNCTEX_INLINE static synctex_iterator_p _synctex_iterator_new(synctex_node_p result, int count) { + synctex_iterator_p iterator; + if ((iterator = _synctex_malloc(sizeof(synctex_iterator_s)))) { + iterator->seed = iterator->top = iterator->next = result; + iterator->count0 = iterator->count = count; + } + return iterator; +}; + +void synctex_iterator_free(synctex_iterator_p iterator) { + if (iterator) { + synctex_node_free(iterator->seed); + _synctex_free(iterator); + } +} +synctex_bool_t synctex_iterator_has_next(synctex_iterator_p iterator) { + return iterator?iterator->count>0:0; +} +int synctex_iterator_count(synctex_iterator_p iterator) { + return iterator? iterator->count: 0; +} + +/** + * The next result of the iterator. + * Internally, the iterator stores handles to nodes. + * Externally, it returns the targets, + * such that the caller only sees nodes. + */ +synctex_node_p synctex_iterator_next_result(synctex_iterator_p iterator) { + if (iterator && iterator->count>0) { + synctex_node_p N = iterator->next; + if(!(iterator->next = _synctex_tree_child(N))) { + iterator->next = iterator->top = __synctex_tree_sibling(iterator->top); + } + --iterator->count; + return _synctex_tree_target(N); + } + return NULL; +} +int synctex_iterator_reset(synctex_iterator_p iterator) { + if (iterator) { + iterator->next = iterator->top = iterator->seed; + return iterator->count = iterator->count0; + } + return 0; +} + +synctex_iterator_p synctex_iterator_new_edit(synctex_scanner_p scanner,int page,float h,float v){ + if (scanner) { + synctex_node_p sheet = NULL; + synctex_point_s hit; + synctex_node_p node = NULL; + synctex_nd_lr_s nds = {{NULL,0},{NULL,0}}; + if (NULL == (scanner = synctex_scanner_parse(scanner)) || 0 >= scanner->unit) {/* scanner->unit must be >0 */ + return NULL; + } + /* Find the proper sheet */ + sheet = synctex_sheet(scanner,page); + if (NULL == sheet) { + return NULL; + } + /* Now sheet points to the sheet node with proper page number. */ + /* Now that scanner has been initialized, we can convert + * the given point to scanner integer coordinates */ + hit = (synctex_point_s) + {(h-scanner->x_offset)/scanner->unit, + (v-scanner->y_offset)/scanner->unit}; + /* At first, we browse all the horizontal boxes of the sheet + * until we find one containing the hit point. */ + if ((node = _synctex_tree_next_hbox(sheet))) { + do { + if (_synctex_point_in_box_v2(&hit,node)) { + /* Maybe the hit point belongs to a contained vertical box. + * This is the most likely situation. + */ + synctex_node_p next = node; +#if defined(SYNCTEX_DEBUG) + printf("--- We are lucky\n"); +#endif + /* This trick is for catching overlapping boxes */ + while ((next = _synctex_tree_next_hbox(next))) { + if (_synctex_point_in_box_v2(&hit,next)) { + node = _synctex_smallest_container_v2(next,node); + } + } + /* node is the smallest horizontal box that contains hit, + * unless there is no hbox at all. + */ + node = _synctex_eq_deepest_container_v2(&hit, node); + nds = _synctex_eq_get_closest_children_in_box_v2(&hit, node); + end: + if (nds.r.node && nds.l.node) { + if ((_synctex_data_tag(nds.r.node)!=_synctex_data_tag(nds.l.node)) + || (_synctex_data_line(nds.r.node)!=_synctex_data_line(nds.l.node)) + || (_synctex_data_column(nds.r.node)!=_synctex_data_column(nds.l.node))) { + if (_synctex_data_line(nds.r.node)<_synctex_data_line(nds.l.node)) { + node = nds.r.node; + nds.r.node = nds.l.node; + nds.l.node = node; + } else if (_synctex_data_line(nds.r.node)==_synctex_data_line(nds.l.node)) { + if (nds.l.distance>nds.r.distance) { + node = nds.r.node; + nds.r.node = nds.l.node; + nds.l.node = node; + } + } + if((node = _synctex_new_handle_with_target(nds.l.node))) { + synctex_node_p other_handle; + if((other_handle = _synctex_new_handle_with_target(nds.r.node))) { + _synctex_tree_set_sibling(node,other_handle); + return _synctex_iterator_new(node,2); + } + return _synctex_iterator_new(node,1); + } + return NULL; + } + /* both nodes have the same input coordinates + * We choose the one closest to the hit point */ + if (nds.l.distance>nds.r.distance) { + nds.l.node = nds.r.node; + } + nds.r.node = NULL; + } else if (nds.r.node) { + nds.l = nds.r; + } else if (!nds.l.node) { + nds.l.node = node; + } + if((node = _synctex_new_handle_with_target(nds.l.node))) { + return _synctex_iterator_new(node,1); + } + return 0; + } + } while ((node = _synctex_tree_next_hbox(node))); + /* All the horizontal boxes have been tested, + * None of them contains the hit point. + */ + } + /* We are not lucky, + * we test absolutely all the node + * to find the closest... */ + if ((node = _synctex_tree_child(sheet))) { +#if defined(SYNCTEX_DEBUG) + printf("--- We are not lucky\n"); +#endif + nds.l = __synctex_closest_deep_child_v2(&hit, node); +#if defined(SYNCTEX_DEBUG) + printf("Edit query best: %i\n", nds.l.distance); +#endif + goto end; + } + } + return NULL; +} + +/** + * Loop the candidate friendly list to find the ones with the proper + * tag and line. + * Returns a tree of results targeting the found candidates. + * At the top level each sibling has its own page number. + * All the results with the same page number are linked by child/parent entry. + * - parameter candidate: a friendly list of candidates + */ +static synctex_node_p _synctex_display_query_v2(synctex_node_p target, int tag, int line, synctex_bool_t exclude_box) { + synctex_node_p first_handle = NULL; + /* Search the first match */ + if (target == NULL) { + return first_handle; + } + do { + int page; + if ((exclude_box + && _synctex_node_is_box(target)) + || (tag != synctex_node_tag(target)) + || (line != synctex_node_line(target))) { + continue; + } + /* We found a first match, create + * a result handle targeting that candidate. */ + first_handle = _synctex_new_handle_with_target(target); + if (first_handle == NULL) { + return first_handle; + } + /* target is either a node, + * or a proxy to some node, in which case, + * the target's target belongs to a form, + * not a sheet. */ + page = synctex_node_page(target); + /* Now create all the other results */ + while ((target = _synctex_tree_friend(target))) { + synctex_node_p result = NULL; + if ((exclude_box + && _synctex_node_is_box(target)) + || (tag != synctex_node_tag(target)) + || (line != synctex_node_line(target))) { + continue; + } + /* Another match, same page number ? */ + result = _synctex_new_handle_with_target(target); + if (NULL == result ) { + return first_handle; + } + /* is it the same page number ? */ + if (synctex_node_page(target) == page) { + __synctex_tree_set_child(result, first_handle); + first_handle = result; + } else { + /* We have 2 page numbers involved */ + __synctex_tree_set_sibling(first_handle, result); + while ((target = _synctex_tree_friend(target))) { + synctex_node_p same_page_node; + if ((exclude_box + && _synctex_node_is_box(target)) + || (tag != synctex_node_tag(target)) + || (line != synctex_node_line(target))) { + continue; + } + /* New match found, which page? */ + result = _synctex_new_handle_with_target(target); + if (NULL == result) { + return first_handle; + } + same_page_node = first_handle; + page = synctex_node_page(target); + /* Find a result with the same page number */; + do { + if (_synctex_node_target_page(same_page_node) == page) { + /* Insert result between same_page_node and its child */ + _synctex_tree_set_child(result,_synctex_tree_set_child(same_page_node,result)); + } else if ((same_page_node = __synctex_tree_sibling(same_page_node))) { + continue; + } else { + /* This is a new page number */ + __synctex_tree_set_sibling(result,first_handle); + first_handle = result; + } + break; + } while (synctex_YES); + } + return first_handle; + } + } + } while ((target = _synctex_tree_friend(target))); + return first_handle; +} +synctex_iterator_p synctex_iterator_new_display(synctex_scanner_p scanner,const char * name,int line,int column, int page_hint) { + SYNCTEX_UNUSED(column) + if (scanner) { + int tag = synctex_scanner_get_tag(scanner,name);/* parse if necessary */ + int max_line = 0; + int line_offset = 1; + int try_count = 100; + synctex_node_p node = NULL; + synctex_node_p result = NULL; + if (tag == 0) { + printf("SyncTeX Warning: No tag for %s\n",name); + return NULL; + } + node = synctex_scanner_input_with_tag(scanner, tag); + max_line = _synctex_data_line(node); + /* node = NULL; */ + if (line>max_line) { + line = max_line; + } + while(try_count--) { + if (line<=max_line) { + /* This loop will only be performed once for advanced viewers */ + synctex_node_p friend = _synctex_scanner_friend(scanner,tag+line); + if ((node = friend)) { + result = _synctex_display_query_v2(node,tag,line,synctex_YES); + if (!result) { + /* We did not find any matching boundary, retry including boxes */ + node = friend;/* no need to test it again, already done */ + result = _synctex_display_query_v2(node,tag,line,synctex_NO); + } + /* Now reverse the order to have nodes in display order, and then keep just a few nodes. + * Order first the best node. */ + /* The result is a tree. At the root level, all nodes + * correspond to different page numbers. + * Each node has a child which corresponds to the same + * page number if relevant. + * Then reorder the nodes to put first the one which fits best. + * The idea is to count the number of nodes + * with the same tag and line number in the parents + * and choose the ones with the biggest count. + */ + if (result) { + /* navigate through siblings, then children */ + synctex_node_p next_sibling = __synctex_tree_reset_sibling(result); + int best_match = abs(page_hint-_synctex_node_target_page(result)); + synctex_node_p sibling; + int match; + synctex_counted_node_s cn = _synctex_vertically_sorted_v2(result); + int count = cn.count; + result = cn.node; + while((sibling = next_sibling)) { + /* What is next? Do not miss that step! */ + next_sibling = __synctex_tree_reset_sibling(sibling); + cn = _synctex_vertically_sorted_v2(sibling); + count += cn.count; + sibling = cn.node; + match = abs(page_hint-_synctex_node_target_page(sibling)); + if (match<best_match) { + /* Order this node first */ + __synctex_tree_set_sibling(sibling,result); + result = sibling; + best_match = match; + } else /*if (match>=best_match)*/ { + __synctex_tree_set_sibling(sibling,__synctex_tree_sibling(result)); + __synctex_tree_set_sibling(result,sibling); + } + } + return _synctex_iterator_new(result,count); + } + } +# if defined(__SYNCTEX_STRONG_DISPLAY_QUERY__) + break; +# else + line += line_offset; + line_offset=line_offset<0?-(line_offset-1):-(line_offset+1); + if (line <= 0) { + line += line_offset; + line_offset=line_offset<0?-(line_offset-1):-(line_offset+1); + } +# endif + } + } + } + return NULL; +} +synctex_status_t synctex_display_query(synctex_scanner_p scanner,const char * name,int line,int column, int page_hint) { + if (scanner) { + synctex_iterator_free(scanner->iterator); + scanner->iterator = synctex_iterator_new_display(scanner, name,line,column, page_hint); + return synctex_iterator_count(scanner->iterator); + } + return SYNCTEX_STATUS_ERROR; +} +synctex_status_t synctex_edit_query(synctex_scanner_p scanner,int page,float h,float v) { + if (scanner) { + synctex_iterator_free(scanner->iterator); + scanner->iterator = synctex_iterator_new_edit(scanner, page, h, v); + return synctex_iterator_count(scanner->iterator); + } + return SYNCTEX_STATUS_ERROR; +} +/** + * The next result of a query. + */ +synctex_node_p synctex_scanner_next_result(synctex_scanner_p scanner) { + return scanner? synctex_iterator_next_result(scanner->iterator): NULL; +} +synctex_status_t synctex_scanner_reset_result(synctex_scanner_p scanner) { + return scanner? synctex_iterator_reset(scanner->iterator): SYNCTEX_STATUS_ERROR; +} + +synctex_node_p synctex_node_target(synctex_node_p node) { + return _synctex_tree_target(node); +} + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Geometric utilities +# endif + +/** Roughly speaking, this is: + * node's h coordinate - hit point's h coordinate. + * If node is to the right of the hit point, then this distance is positive, + * if node is to the left of the hit point, this distance is negative. + * If the argument is a pdf form reference, then the child is used and returned instead. + * Last Revision: Mon Apr 24 07:05:27 UTC 2017 + */ +static synctex_nd_s _synctex_point_h_ordered_distance_v2 +(synctex_point_p hit, synctex_node_p node) { + synctex_nd_s nd = {node,INT_MAX}; + if (node) { + int min,med,max,width; + switch(synctex_node_type(node)) { + /* The distance between a point and a box is special. + * It is not the euclidean distance, nor something similar. + * We have to take into account the particular layout, + * and the box hierarchy. + * Given a box, there are 9 regions delimited by the lines of the edges of the box. + * The origin being at the top left corner of the page, + * we also give names to the vertices of the box. + * + * 1 | 2 | 3 + * ---A---B---> + * 4 | 5 | 6 + * ---C---D---> + * 7 | 8 | 9 + * v v + */ + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_void_hbox: + /* getting the box bounds, taking into account negative width, height and depth. */ + width = _synctex_data_width(node); + min = _synctex_data_h(node); + max = min + (width>0?width:-width); + /* We always have min <= max */ + if (hit->h<min) { + nd.distance = min - hit->h; /* regions 1+4+7, result is > 0 */ + } else if (hit->h>max) { + nd.distance = max - hit->h; /* regions 3+6+9, result is < 0 */ + } else { + nd.distance = 0; /* regions 2+5+8, inside the box, except for vertical coordinates */ + } + break; + case synctex_node_type_proxy_vbox: + /* getting the box bounds, taking into account negative width, height and depth. */ + width = synctex_node_width(node); + min = synctex_node_h(node); + max = min + (width>0?width:-width); + /* We always have min <= max */ + if (hit->h<min) { + nd.distance = min - hit->h; /* regions 1+4+7, result is > 0 */ + } else if (hit->h>max) { + nd.distance = max - hit->h; /* regions 3+6+9, result is < 0 */ + } else { + nd.distance = 0; /* regions 2+5+8, inside the box, except for vertical coordinates */ + } + break; + case synctex_node_type_hbox: + case synctex_node_type_proxy_hbox: + /* getting the box bounds, taking into account negative width, height and depth. */ + width = synctex_node_hbox_width(node); + min = synctex_node_hbox_h(node); + max = min + (width>0?width:-width); + /* We always have min <= max */ + if (hit->h<min) { + nd.distance = min - hit->h; /* regions 1+4+7, result is > 0 */ + } else if (hit->h>max) { + nd.distance = max - hit->h; /* regions 3+6+9, result is < 0 */ + } else { + nd.distance = 0; /* regions 2+5+8, inside the box, except for vertical coordinates */ + } + break; + case synctex_node_type_kern: + /* IMPORTANT NOTICE: the location of the kern is recorded AFTER the move. + * The distance to the kern is very special, + * in general, there is no text material in the kern, + * this is why we compute the offset relative to the closest edge of the kern.*/ + max = _synctex_data_width(node); + if (max<0) { + min = _synctex_data_h(node); + max = min - max; + } else { + min = -max; + max = _synctex_data_h(node); + min += max; + } + med = (min+max)/2; + /* positive kern: '.' means text, '>' means kern offset + * ............. + * min>>>>med>>>>max + * ............... + * negative kern: '.' means text, '<' means kern offset + * ............................ + * min<<<<med<<<<max + * ................................. + * Actually, we do not take into account negative widths. + * There is a problem for such situation when there is effectively overlapping text. + * But this should be extremely rare. I guess that in that case, many different choices + * could be made, one being in contradiction with the other. + * It means that the best choice should be made according to the situation that occurs + * most frequently. + */ + if (hit->h<min) { + nd.distance = min - hit->h + 1; /* penalty to ensure other nodes are chosen first in case of overlapping ones */ + } else if (hit->h>max) { + nd.distance = max - hit->h - 1; /* same kind of penalty */ + } else if (hit->h>med) { + /* do things like if the node had 0 width and was placed at the max edge + 1*/ + nd.distance = max - hit->h + 1; /* positive, the kern is to the right of the hit point */ + } else { + nd.distance = min - hit->h - 1; /* negative, the kern is to the left of the hit point */ + } + break; + case synctex_node_type_rule:/* to do: special management */ + case synctex_node_type_glue: + case synctex_node_type_math: + case synctex_node_type_boundary: + case synctex_node_type_box_bdry: + nd.distance = _synctex_data_h(node) - hit->h; + break; + case synctex_node_type_ref: + nd.node = synctex_node_child(node); + nd = _synctex_point_h_ordered_distance_v2(hit,nd.node); + break; + case synctex_node_type_proxy: + case synctex_node_type_proxy_last: + { + /* shift the hit point to be relative to the proxy origin, + * then compute the distance to the target + */ + synctex_point_s otherHit = *hit; + otherHit.h -= _synctex_data_h(node); + otherHit.v -= _synctex_data_v(node); + nd.node = _synctex_tree_target(node); + nd = _synctex_point_h_ordered_distance_v2(&otherHit,nd.node); + nd.node = node; + } + default: + break; + } + } + return nd; +} +/** Roughly speaking, this is: + * node's v coordinate - hit point's v coordinate. + * If node is at the top of the hit point, then this distance is positive, + * if node is at the bottom of the hit point, this distance is negative. + */ +static synctex_nd_s _synctex_point_v_ordered_distance_v2 +(synctex_point_p hit, synctex_node_p node) { + synctex_nd_s nd = {node, INT_MAX}; + int min,max,depth,height; + switch(synctex_node_type(node)) { + /* The distance between a point and a box is special. + * It is not the euclidean distance, nor something similar. + * We have to take into account the particular layout, + * and the box hierarchy. + * Given a box, there are 9 regions delimited by the lines of the edges of the box. + * The origin being at the top left corner of the page, + * we also give names to the vertices of the box. + * + * 1 | 2 | 3 + * ---A---B---> + * 4 | 5 | 6 + * ---C---D---> + * 7 | 8 | 9 + * v v + */ + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_void_hbox: + /* getting the box bounds, taking into account negative width, height and depth. */ + min = synctex_node_v(node); + max = min + _synctex_abs(_synctex_data_depth(node)); + min -= _synctex_abs(_synctex_data_height(node)); + /* We always have min <= max */ + if (hit->v<min) { + nd.distance = min - hit->v; /* regions 1+2+3, result is > 0 */ + } else if (hit->v>max) { + nd.distance = max - hit->v; /* regions 7+8+9, result is < 0 */ + } else { + nd.distance = 0; /* regions 4.5.6, inside the box, except for horizontal coordinates */ + } + break; + case synctex_node_type_proxy_vbox: + /* getting the box bounds, taking into account negative width, height and depth. */ + min = synctex_node_v(node); + max = min + _synctex_abs(synctex_node_depth(node)); + min -= _synctex_abs(synctex_node_height(node)); + /* We always have min <= max */ + if (hit->v<min) { + nd.distance = min - hit->v; /* regions 1+2+3, result is > 0 */ + } else if (hit->v>max) { + nd.distance = max - hit->v; /* regions 7+8+9, result is < 0 */ + } else { + nd.distance = 0; /* regions 4.5.6, inside the box, except for horizontal coordinates */ + } + break; + case synctex_node_type_hbox: + case synctex_node_type_proxy_hbox: + /* getting the box bounds, taking into account negative height and depth. */ + min = synctex_node_hbox_v(node); + depth = synctex_node_hbox_depth(node); + max = min + (depth>0?depth:-depth); + height = synctex_node_hbox_height(node); + min -= (height>0?height:-height); + /* We always have min <= max */ + if (hit->v<min) { + nd.distance = min - hit->v; /* regions 1+2+3, result is > 0 */ + } else if (hit->v>max) { + nd.distance = max - hit->v; /* regions 7+8+9, result is < 0 */ + } else { + nd.distance = 0; /* regions 4.5.6, inside the box, except for horizontal coordinates */ + } + break; + case synctex_node_type_rule:/* to do: special management */ + case synctex_node_type_kern: + case synctex_node_type_glue: + case synctex_node_type_math: + min = _synctex_data_v(node); + max = min + _synctex_abs(_synctex_data_depth(_synctex_tree_parent(node))); + min -= _synctex_abs(_synctex_data_height(_synctex_tree_parent(node))); + /* We always have min <= max */ + if (hit->v<min) { + nd.distance = min - hit->v; /* regions 1+2+3, result is > 0 */ + } else if (hit->v>max) { + nd.distance = max - hit->v; /* regions 7+8+9, result is < 0 */ + } else { + nd.distance = 0; /* regions 4.5.6, inside the box, except for horizontal coordinates */ + } + break; + case synctex_node_type_ref: + nd.node = synctex_node_child(node); + nd = _synctex_point_v_ordered_distance_v2(hit,nd.node); + break; + case synctex_node_type_proxy: + case synctex_node_type_proxy_last: + { + synctex_point_s otherHit = *hit; + otherHit.h -= _synctex_data_h(node); + otherHit.v -= _synctex_data_v(node); + nd.node = _synctex_tree_target(node); + nd = _synctex_point_v_ordered_distance_v2(&otherHit,nd.node); + nd.node = node; + } + default: break; + } + return nd; +} +/** + * The best is the one with the smallest area. + * The area is width*height where width and height may be big. + * So there is a real risk of overflow if we stick with ints. + */ +SYNCTEX_INLINE static synctex_node_p _synctex_smallest_container_v2(synctex_node_p node, synctex_node_p other_node) { + long total_height, other_total_height; + unsigned long area, other_area; + long width = synctex_node_hbox_width(node); + long other_width = synctex_node_hbox_width(other_node); + if (width<0) { + width = -width; + } + if (other_width<0) { + other_width = -other_width; + } + total_height = _synctex_abs(synctex_node_hbox_depth(node)) + _synctex_abs(synctex_node_hbox_height(node)); + other_total_height = _synctex_abs(synctex_node_hbox_depth(other_node)) + _synctex_abs(synctex_node_hbox_height(other_node)); + area = total_height*width; + other_area = other_total_height*other_width; + if (area<other_area) { + return node; + } + if (area>other_area) { + return other_node; + } + if (_synctex_abs(_synctex_data_width(node))>_synctex_abs(_synctex_data_width(other_node))) { + return node; + } + if (_synctex_abs(_synctex_data_width(node))<_synctex_abs(_synctex_data_width(other_node))) { + return other_node; + } + if (total_height<other_total_height) { + return node; + } + if (total_height>other_total_height) { + return other_node; + } + return node; +} + +SYNCTEX_INLINE static synctex_bool_t _synctex_point_in_box_v2(synctex_point_p hit, synctex_node_p node) { + if (node) { + if (0 == _synctex_point_h_ordered_distance_v2(hit,node).distance + && 0 == _synctex_point_v_ordered_distance_v2(hit,node).distance) { + return synctex_YES; + } + } + return synctex_NO; +} + +static int _synctex_distance_to_box_v2(synctex_point_p hit,synctex_box_p box) { + /* The distance between a point and a box is special. + * It is not the euclidean distance, nor something similar. + * We have to take into account the particular layout, + * and the box hierarchy. + * Given a box, there are 9 regions delimited by the lines of the edges of the box. + * The origin being at the top left corner of the page, + * we also give names to the vertices of the box. + * + * 1 | 2 | 3 + * ---A---B---> + * 4 | 5 | 6 + * ---C---D---> + * 7 | 8 | 9 + * v v + * In each region, there is a different formula. + * In the end we have a continuous distance which may not be a mathematical distance but who cares. */ + if (hit->v<box->min.v) { + /* Regions 1, 2 or 3 */ + if (hit->h<box->min.h) { + /* This is region 1. The distance to the box is the L1 distance PA. */ + return box->min.v - hit->v + box->min.h - hit->h;/* Integer overflow? probability epsilon */ + } else if (hit->h<=box->max.h) { + /* This is region 2. The distance to the box is the geometrical distance to the top edge. */ + return box->min.v - hit->v; + } else { + /* This is region 3. The distance to the box is the L1 distance PB. */ + return box->min.v - hit->v + hit->h - box->max.h; + } + } else if (hit->v<=box->max.v) { + /* Regions 4, 5 or 6 */ + if (hit->h<box->min.h) { + /* This is region 4. The distance to the box is the geometrical distance to the left edge. */ + return box->min.h - hit->h; + } else if (hit->h<=box->max.h) { + /* This is region 5. We are inside the box. */ + return 0; + } else { + /* This is region 6. The distance to the box is the geometrical distance to the right edge. */ + return hit->h - box->max.h; + } + } else { + /* Regions 7, 8 or 9 */ + if (hit->h<box->min.h) { + /* This is region 7. The distance to the box is the L1 distance PC. */ + return hit->v - box->max.v + box->min.h - hit->h; + } else if (hit->h<=box->max.h) { + /* This is region 8. The distance to the box is the geometrical distance to the top edge. */ + return hit->v - box->max.v; + } else { + /* This is region 9. The distance to the box is the L1 distance PD. */ + return hit->v - box->max.v + hit->h - box->max.h; + } + } +} + +/** + * The distance from the hit point to the node. + */ +static int _synctex_point_node_distance_v2(synctex_point_p hit, synctex_node_p node) { + int d = INT_MAX; + if (node) { + synctex_box_s box = {{0,0},{0,0}}; + int dd = INT_MAX; + switch(synctex_node_type(node)) { + case synctex_node_type_vbox: + box.min.h = _synctex_data_h(node); + box.max.h = box.min.h + _synctex_abs(_synctex_data_width(node)); + box.min.v = synctex_node_v(node); + box.max.v = box.min.v + _synctex_abs(_synctex_data_depth(node)); + box.min.v -= _synctex_abs(_synctex_data_height(node)); + return _synctex_distance_to_box_v2(hit,&box); + case synctex_node_type_proxy_vbox: + box.min.h = synctex_node_h(node); + box.max.h = box.min.h + _synctex_abs(synctex_node_width(node)); + box.min.v = synctex_node_v(node); + box.max.v = box.min.v + _synctex_abs(synctex_node_depth(node)); + box.min.v -= _synctex_abs(synctex_node_height(node)); + return _synctex_distance_to_box_v2(hit,&box); + case synctex_node_type_hbox: + case synctex_node_type_proxy_hbox: + box.min.h = synctex_node_hbox_h(node); + box.max.h = box.min.h + _synctex_abs(synctex_node_hbox_width(node)); + box.min.v = synctex_node_hbox_v(node); + box.max.v = box.min.v + _synctex_abs(synctex_node_hbox_depth(node)); + box.min.v -= _synctex_abs(synctex_node_hbox_height(node)); + return _synctex_distance_to_box_v2(hit,&box); + case synctex_node_type_void_vbox: + case synctex_node_type_void_hbox: + /* best of distances from the left edge and right edge*/ + box.min.h = _synctex_data_h(node); + box.max.h = box.min.h; + box.min.v = _synctex_data_v(node); + box.max.v = box.min.v + _synctex_abs(_synctex_data_depth(node)); + box.min.v -= _synctex_abs(_synctex_data_height(node)); + d = _synctex_distance_to_box_v2(hit,&box); + box.min.h = box.min.h + _synctex_abs(_synctex_data_width(node)); + box.max.h = box.min.h; + dd = _synctex_distance_to_box_v2(hit,&box); + return d<dd ? d:dd; + case synctex_node_type_kern: + box.min.h = _synctex_data_h(node); + box.max.h = box.min.h; + box.max.v = _synctex_data_v(node); + box.min.v = box.max.v - _synctex_abs(_synctex_data_height(_synctex_tree_parent(node))); + d = _synctex_distance_to_box_v2(hit,&box); + box.min.h -= _synctex_data_width(node); + box.max.h = box.min.h; + dd = _synctex_distance_to_box_v2(hit,&box); + return d<dd ? d:dd; + case synctex_node_type_glue: + case synctex_node_type_math: + case synctex_node_type_boundary: + case synctex_node_type_box_bdry: + box.min.h = _synctex_data_h(node); + box.max.h = box.min.h; + box.max.v = _synctex_data_v(node); + box.min.v = box.max.v - _synctex_abs(_synctex_data_height(_synctex_tree_parent(node))); + return _synctex_distance_to_box_v2(hit,&box); + case synctex_node_type_proxy: + case synctex_node_type_proxy_last: + { + synctex_point_s otherHit = *hit; + otherHit.h -= _synctex_data_h(node); + otherHit.v -= _synctex_data_v(node); + return _synctex_point_node_distance_v2(&otherHit, _synctex_tree_target(node)); + } + default: break; + } + } + return d; +} +static synctex_node_p _synctex_eq_deepest_container_v2(synctex_point_p hit, synctex_node_p node) { + if (node) { + /**/ + synctex_node_p child; + if ((child = synctex_node_child(node))) { + /* Non void hbox or vbox, form ref or proxy */ + /* We go deep first because some boxes have 0 dimensions + * despite they do contain some black material. + */ + do { + if ((_synctex_point_in_box_v2(hit,child))) { + synctex_node_p deep = _synctex_eq_deepest_container_v2(hit,child); + if (deep) { + /* One of the children contains the hit. */ + return deep; + } + } + } while((child = synctex_node_sibling(child))); + /* is the hit point inside the box? */ + if (synctex_node_type(node) == synctex_node_type_vbox + || synctex_node_type(node) == synctex_node_type_proxy_vbox) { + /* For vboxes we try to use some node inside. + * Walk through the list of siblings until we find the closest one. + * Only consider siblings with children inside. */ + if ((child = _synctex_tree_child(node))) { + synctex_nd_s best = SYNCTEX_ND_0; + do { + if (_synctex_tree_child(child)) { + int d = _synctex_point_node_distance_v2(hit,child); + if (d <= best.distance) { + best = (synctex_nd_s){child, d}; + } + } + } while((child = __synctex_tree_sibling(child))); + if (best.node) { + return best.node; + } + } + } + if (_synctex_point_in_box_v2(hit,node)) { + return node; + } + } + } + return NULL; +} +static synctex_nd_s _synctex_eq_deepest_container_v3(synctex_point_p hit, synctex_node_p node) { + if (node) { + synctex_node_p child = NULL; + if ((child = synctex_node_child(node))) { + /* Non void hbox, vbox, box proxy or form ref */ + /* We go deep first because some boxes have 0 dimensions + * despite they do contain some black material. + */ + do { + synctex_nd_s deep = _synctex_eq_deepest_container_v3(hit, child); + if (deep.node) { + /* One of the children contains the hit-> */ + return deep; + } + } while((child = synctex_node_sibling(child))); + /* For vboxes we try to use some node inside. + * Walk through the list of siblings until we find the closest one. + * Only consider siblings with children inside. */ + if (synctex_node_type(node) == synctex_node_type_vbox + || synctex_node_type(node) == synctex_node_type_proxy_vbox) { + if ((child = synctex_node_child(node))) { + synctex_nd_s best = SYNCTEX_ND_0; + do { + if (synctex_node_child(child)) { + int d = _synctex_point_node_distance_v2(hit,child); + if (d < best.distance) { + best = (synctex_nd_s){child,d}; + } + } + } while((child = synctex_node_sibling(child))); + if (best.node) { + return best; + } + } + } + /* is the hit point inside the box? */ + if (_synctex_point_in_box_v2(hit,node)) { + return (synctex_nd_s){node, 0}; + } + } + } + return SYNCTEX_ND_0; +} + +/* Compares the locations of the hit point with the locations of + * the various nodes contained in the box. + * As it is an horizontal box, we only compare horizontal coordinates. + */ +SYNCTEX_INLINE static synctex_nd_lr_s __synctex_eq_get_closest_children_in_hbox_v2(synctex_point_p hitP, synctex_node_p node) { + synctex_nd_s childd = SYNCTEX_ND_0; + synctex_nd_lr_s nds = {SYNCTEX_ND_0,SYNCTEX_ND_0}; + if ((childd.node = synctex_node_child(node))) { + synctex_nd_s nd = SYNCTEX_ND_0; + do { + childd = _synctex_point_h_ordered_distance_v2(hitP,childd.node); + if (childd.distance > 0) { + /* node is to the right of the hit point. + * We compare node and the previously recorded one, through the recorded distance. + * If the nodes have the same tag, prefer the one with the smallest line number, + * if the nodes also have the same line number, prefer the one with the smallest column. */ + if (nds.r.distance > childd.distance) { + nds.r = childd; + } else if (nds.r.distance == childd.distance && nds.r.node) { + if (_synctex_data_tag(nds.r.node) == _synctex_data_tag(childd.node) + && (_synctex_data_line(nds.r.node) > _synctex_data_line(childd.node) + || (_synctex_data_line(nds.r.node) == _synctex_data_line(childd.node) + && _synctex_data_column(nds.r.node) > _synctex_data_column(childd.node)))) { + nds.r = childd; + } + } + } else if (childd.distance == 0) { + /* hit point is inside node. */ + if (_synctex_tree_child(childd.node)) { + return _synctex_eq_get_closest_children_in_box_v2(hitP, childd.node); + } + nds.l = childd; + } else { /* here childd.distance < 0, the hit point is to the right of node */ + childd.distance = -childd.distance; + if (nds.l.distance > childd.distance) { + nds.l = childd; + } else if (nds.l.distance == childd.distance && nds.l.node) { + if (_synctex_data_tag(nds.l.node) == _synctex_data_tag(childd.node) + && (_synctex_data_line(nds.l.node) > _synctex_data_line(childd.node) + || (_synctex_data_line(nds.l.node) == _synctex_data_line(childd.node) + && _synctex_data_column(nds.l.node) > _synctex_data_column(childd.node)))) { + nds.l = childd; + } + } + } + } while((childd.node = synctex_node_sibling(childd.node))); + if (nds.l.node) { + /* the left node is new, try to narrow the result */ + if ((nd = _synctex_eq_deepest_container_v3(hitP,nds.l.node)).node) { + nds.l = nd; + } + if((nd = __synctex_closest_deep_child_v2(hitP,nds.l.node)).node) { + nds.l.node = nd.node; + } + } + if (nds.r.node) { + /* the right node is new, try to narrow the result */ + if ((nd = _synctex_eq_deepest_container_v3(hitP,nds.r.node)).node) { + nds.r = nd; + } + if((nd = __synctex_closest_deep_child_v2(hitP,nds.r.node)).node) { + nds.r.node = nd.node; + } + } + } + return nds; +} + +#if 0 +SYNCTEX_INLINE static synctex_nd_lr_s __synctex_eq_get_closest_children_in_hbox_v3(synctex_point_p hitP, synctex_node_p nodeP) { + synctex_nd_s nd = SYNCTEX_ND_0; + synctex_nd_lr_s nds = {SYNCTEX_ND_0,SYNCTEX_ND_0}; + if ((nd.node = _synctex_tree_child(nodeP))) { + do { + nd = _synctex_point_h_ordered_distance_v2(hitP,nd.node); + if (nd.distance > 0) { + /* node is to the right of the hit point. + * We compare node and the previously recorded one, through the recorded distance. + * If the nodes have the same tag, prefer the one with the smallest line number, + * if the nodes also have the same line number, prefer the one with the smallest column. */ + if (nds.r.distance > nd.distance) { + nds.r = nd; + } else if (nds.r.distance == nd.distance && nds.r.node) { + if (_synctex_data_tag(nds.r.node) == _synctex_data_tag(nd.node) + && (_synctex_data_line(nds.r.node) > _synctex_data_line(nd.node) + || (_synctex_data_line(nds.r.node) == _synctex_data_line(nd.node) + && _synctex_data_column(nds.r.node) > _synctex_data_column(nd.node)))) { + nds.r = nd; + } + } + } else if (nd.distance == 0) { + /* hit point is inside node. */ + nds.l = nd; + } else { /* here nd.d < 0, the hit point is to the right of node */ + nd.distance = -nd.distance; + if (nds.l.distance > nd.distance) { + nds.l = nd; + } else if (nds.l.distance == nd.distance && nds.l.node) { + if (_synctex_data_tag(nds.l.node) == _synctex_data_tag(nd.node) + && (_synctex_data_line(nds.l.node) > _synctex_data_line(nd.node) + || (_synctex_data_line(nds.l.node) == _synctex_data_line(nd.node) + && _synctex_data_column(nds.l.node) > _synctex_data_column(nd.node)))) { + nds.l = nd; + } + } + } + } while((nd.node = __synctex_tree_sibling(nd.node))); + if (nds.l.node) { + /* the left node is new, try to narrow the result */ + if ((nd.node = _synctex_eq_deepest_container_v2(hitP,nds.l.node))) { + nds.l.node = nd.node; + } + if((nd = _synctex_eq_closest_child_v2(hitP,nds.l.node)).node) { + nds.l.node = nd.node; + } + } + if (nds.r.node) { + /* the right node is new, try to narrow the result */ + if ((nd.node = _synctex_eq_deepest_container_v2(hitP,nds.r.node))) { + nds.r.node = nd.node; + } + if((nd = _synctex_eq_closest_child_v2(hitP,nds.r.node)).node) { + nds.r.node = nd.node; + } + } + } + return nds; +} +#endif +SYNCTEX_INLINE static synctex_nd_lr_s __synctex_eq_get_closest_children_in_vbox_v2(synctex_point_p hitP, synctex_node_p nodeP) { + SYNCTEX_UNUSED(nodeP) + synctex_nd_lr_s nds = {SYNCTEX_ND_0,SYNCTEX_ND_0}; + synctex_nd_s nd = SYNCTEX_ND_0; + if ((nd.node = synctex_node_child(nd.node))) { + do { + nd = _synctex_point_v_ordered_distance_v2(hitP,nd.node); + /* this is what makes the difference with the h version above */ + if (nd.distance > 0) { + /* node is to the top of the hit point (below because TeX is oriented from top to bottom. + * We compare node and the previously recorded one, through the recorded distance. + * If the nodes have the same tag, prefer the one with the smallest line number, + * if the nodes also have the same line number, prefer the one with the smallest column. */ + if (nds.r.distance > nd.distance) { + nds.r = nd; + } else if (nds.r.distance == nd.distance && nds.r.node) { + if (_synctex_data_tag(nds.r.node) == _synctex_data_tag(nd.node) + && (_synctex_data_line(nds.r.node) > _synctex_data_line(nd.node) + || (_synctex_data_line(nds.r.node) == _synctex_data_line(nd.node) + && _synctex_data_column(nds.r.node) > _synctex_data_column(nd.node)))) { + nds.r = nd; + } + } + } else if (nd.distance == 0) { + nds.l = nd; + } else { /* here nd < 0 */ + nd.distance = -nd.distance; + if (nds.l.distance > nd.distance) { + nds.l = nd; + } else if (nds.l.distance == nd.distance && nds.l.node) { + if (_synctex_data_tag(nds.l.node) == _synctex_data_tag(nd.node) + && (_synctex_data_line(nds.l.node) > _synctex_data_line(nd.node) + || (_synctex_data_line(nds.l.node) == _synctex_data_line(nd.node) + && _synctex_data_column(nds.l.node) > _synctex_data_column(nd.node)))) { + nds.l = nd; + } + } + } + } while((nd.node = synctex_node_sibling(nd.node))); + if (nds.l.node) { + if ((nd.node = _synctex_eq_deepest_container_v2(hitP,nds.l.node))) { + nds.l.node = nd.node; + } + if((nd = _synctex_eq_closest_child_v2(hitP,nds.l.node)).node) { + nds.l.node = nd.node; + } + } + if (nds.r.node) { + if ((nd.node = _synctex_eq_deepest_container_v2(hitP,nds.r.node))) { + nds.r.node = nd.node; + } + if((nd = _synctex_eq_closest_child_v2(hitP,nds.r.node)).node) { + nds.r.node = nd.node; + } + } + } + return nds; +} + +/** + * Get the child closest to the hit point. + * - parameter: hit point + * - parameter: containing node + * - returns: the child and the distance to the hit point. + * SYNCTEX_ND_0 if the parameter node has no children. + * - note: recursive call. + */ +static synctex_nd_s __synctex_closest_deep_child_v2(synctex_point_p hitP, synctex_node_p node) { + synctex_nd_s best = SYNCTEX_ND_0; + synctex_node_p child = NULL; + if ((child = synctex_node_child(node))) { +#if defined(SYNCTEX_DEBUG) + printf("Closest deep child on box at line %i\n", + SYNCTEX_LINEINDEX(node)); +#endif + do { +#if SYNCTEX_DEBUG>500 + synctex_node_display(child); +#endif + synctex_nd_s nd = SYNCTEX_ND_0; + if (_synctex_node_is_box(child)) { + nd = __synctex_closest_deep_child_v2(hitP,child); + } else { + nd = (synctex_nd_s) {child, _synctex_point_node_distance_v2(hitP,child)}; + } + if (nd.distance < best.distance ||(nd.distance == best.distance + && synctex_node_type(nd.node) != synctex_node_type_kern)) { +#if defined(SYNCTEX_DEBUG) + if(nd.node) { + printf("New best %i<=%i line %i\n",nd.distance, + best.distance,SYNCTEX_LINEINDEX(nd.node)); + } +#endif + best = nd; + } + } while((child = synctex_node_sibling(child))); +#if defined(SYNCTEX_DEBUG) + if(best.node) { + printf("Found new best %i line %i\n",best.distance,SYNCTEX_LINEINDEX(best.node)); + } +#endif + } + return best; +} + +/** + * Return the closest child. + * - parameter: a pointer to the hit point, + * - parameter: the container + * - return: SYNCTEX_ND_0 if node has no child, + * the __synctex_closest_deep_child_v2 otherwise. + */ +static synctex_nd_s _synctex_eq_closest_child_v2(synctex_point_p hitP, synctex_node_p node) { + synctex_nd_s nd = SYNCTEX_ND_0; + if (_synctex_node_is_box(node)) { + nd = __synctex_closest_deep_child_v2(hitP, node); + if (_synctex_node_is_box(nd.node)) { + synctex_node_p child = NULL; + if ((child = synctex_node_child(nd.node))) { + synctex_nd_s best = {child,_synctex_point_node_distance_v2(hitP,child)}; + while((child = synctex_node_sibling(child))) { + int d = _synctex_point_node_distance_v2(hitP,child); + if (d < best.distance) { + best = (synctex_nd_s){child,d}; + } else if (d == best.distance && synctex_node_type(child) != synctex_node_type_kern) { + best.node = child; + } + } + return best; + } + } + return nd; + } + return SYNCTEX_ND_0; +} +SYNCTEX_INLINE static synctex_nd_lr_s _synctex_eq_get_closest_children_in_box_v2(synctex_point_p hitP, synctex_node_p node) { + synctex_nd_lr_s nds = {SYNCTEX_ND_0,SYNCTEX_ND_0}; + if(_synctex_tree_has_child(node)) { /* node != NULL */ + if (node->class_->type==synctex_node_type_hbox || + node->class_->type==synctex_node_type_proxy_hbox) { + return __synctex_eq_get_closest_children_in_hbox_v2(hitP,node); + } else { + return __synctex_eq_get_closest_children_in_vbox_v2(hitP,node); + } + } + return nds; +} + +#ifndef SYNCTEX_NO_UPDATER + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Updater +# endif + +typedef int (*synctex_print_f)(synctex_updater_p, const char * , ...); /* print formatted to either FILE * or gzFile */ +typedef void (*synctex_close_f)(synctex_updater_p); /* close FILE * or gzFile */ + +# define SYNCTEX_BITS_PER_BYTE 8 + +typedef union { + gzFile as_gzFile; + FILE * as_FILE_p; + void * as_ptr; +} syncex_file_u; + +struct synctex_updater_t { + syncex_file_u file; + synctex_print_f print; + synctex_close_f close; + int length; /* the number of chars appended */ +}; + +static int _synctex_updater_print(synctex_updater_p updater, const char * format, ...) { + int result = 0; + if (updater) { + va_list va; + va_start(va, format); + result = vfprintf(updater->file.as_FILE_p, + format, + va); + va_end(va); + } + return result; +} +#if defined(_MSC_VER) +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> + +static int vasprintf(char **ret, + const char *format, + va_list ap) +{ + int len; + len = _vsnprintf(NULL, 0, format, ap); + if (len < 0) return -1; + *ret = malloc(len + 1); + if (!*ret) return -1; + _vsnprintf(*ret, len+1, format, ap); + (*ret)[len] = '\0'; + return len; +} + +#endif + +/** + * gzvprintf is not available until OSX 10.10 + */ +static int _synctex_updater_print_gz(synctex_updater_p updater, const char * format, ...) { + int result = 0; + if (updater) { + char * buffer; + va_list va; + va_start(va, format); + if (vasprintf(&buffer, format, va) < 0) { + _synctex_error("Out of memory..."); + } else if ((result = (int)strlen(buffer))) { + result = gzwrite(updater->file.as_gzFile, buffer, (unsigned)result); + } + va_end(va); + free(buffer); + } + return result; +} + +static void _synctex_updater_close(synctex_updater_p updater) { + if (updater) { + fclose(updater->file.as_FILE_p); + } +} + +static void _synctex_updater_close_gz(synctex_updater_p updater) { + if (updater) { + gzclose(updater->file.as_gzFile); + } +} + +synctex_updater_p synctex_updater_new_with_output_file(const char * output, const char * build_directory) { + synctex_updater_p updater = NULL; + const char * mode = NULL; + synctex_open_s open; + /* prepare the updater, the memory is the only one dynamically allocated */ + updater = (synctex_updater_p)_synctex_malloc(sizeof(synctex_updater_s)); + if (NULL == updater) { + _synctex_error("! synctex_updater_new_with_file: malloc problem"); + return NULL; + } + open = _synctex_open_v2(output,build_directory,0,synctex_ADD_QUOTES); + if (open.status < SYNCTEX_STATUS_OK) { + open = _synctex_open_v2(output,build_directory,0,synctex_DONT_ADD_QUOTES); + if (open.status < SYNCTEX_STATUS_OK) { + return_on_error: + _synctex_free(updater); + return updater = NULL; + } + } + /* OK, the file exists, we close it and reopen it with the correct mode. + * The receiver is now the owner of the "synctex" variable. */ + gzclose(open.file); + updater->file.as_ptr = NULL; + mode = _synctex_get_io_mode_name(open.io_mode|synctex_io_append_mask);/* either "a" or "ab", depending on the file extension */ + if (open.io_mode&synctex_io_gz_mask) { + if (NULL == (updater->file.as_FILE_p = fopen(open.synctex,mode))) { + no_write_error: + _synctex_error("! synctex_updater_new_with_file: Can't append to %s",open.synctex); + free(open.synctex); + goto return_on_error; + } + updater->print = &_synctex_updater_print; + updater->close = &_synctex_updater_close; + } else { + if (NULL == (updater->file.as_gzFile = gzopen(open.synctex,mode))) { + goto no_write_error; + } + updater->print = &_synctex_updater_print_gz; + updater->close = &_synctex_updater_close_gz; + } + printf("SyncTeX: updating %s...",open.synctex); + _synctex_free(open.synctex); + return updater; +} + +void synctex_updater_append_magnification(synctex_updater_p updater, char * magnification){ + if (NULL==updater) { + return; + } + if (magnification && strlen(magnification)) { + updater->length += + updater->print(updater,"Magnification:%s\n",magnification); + } +} + +void synctex_updater_append_x_offset(synctex_updater_p updater, char * x_offset){ + if (NULL==updater) { + return; + } + if (x_offset && strlen(x_offset)) { + updater->length += updater->print(updater,"X Offset:%s\n",x_offset); + } +} + +void synctex_updater_append_y_offset(synctex_updater_p updater, char * y_offset){ + if (NULL==updater) { + return; + } + if (y_offset && strlen(y_offset)) { + updater->length += updater->print(updater,"Y Offset:%s\n",y_offset); + } +} + +void synctex_updater_free(synctex_updater_p updater){ + if (NULL==updater) { + return; + } + if (updater->length>0) { + updater->print(updater,"!%i\n",updater->length); + } + updater->close(updater); + _synctex_free(updater); + printf("... done.\n"); + return; +} +#endif + +#if defined(SYNCTEX_TESTING) +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Testers +# endif +static int _synctex_input_copy_name(synctex_node_p input, char * name) { + char * copy = _synctex_malloc(strlen(name)+1); + memcpy(copy,name,strlen(name)+1); + _synctex_data_set_name(input,copy); + return 0; +} +int synctex_test_setup_scanner_sheets_421(synctex_scanner_p scanner) { + int TC = 0; + synctex_node_p sheet = synctex_node_new(scanner,synctex_node_type_sheet); + _synctex_data_set_page(sheet,4); + SYNCTEX_TEST_BODY(TC, _synctex_data_page(sheet)==4,""); + synctex_node_free(scanner->sheet); + scanner->sheet = sheet; + sheet = synctex_node_new(scanner,synctex_node_type_sheet); + _synctex_data_set_page(sheet,2); + SYNCTEX_TEST_BODY(TC, _synctex_data_page(sheet)==2,""); + __synctex_tree_set_sibling(sheet, scanner->sheet); + scanner->sheet = sheet; + sheet = synctex_node_new(scanner,synctex_node_type_sheet); + _synctex_data_set_page(sheet,1); + SYNCTEX_TEST_BODY(TC, _synctex_data_page(sheet)==1,""); + __synctex_tree_set_sibling(sheet, scanner->sheet); + scanner->sheet = sheet; + return TC; +} +int synctex_test_input(synctex_scanner_p scanner) { + int TC = 0; + synctex_node_p input = synctex_node_new(scanner,synctex_node_type_input); + _synctex_data_set_tag(input,421); + SYNCTEX_TEST_BODY(TC, _synctex_data_tag(input)==421,""); + _synctex_data_set_tag(input,124); + SYNCTEX_TEST_BODY(TC, _synctex_data_tag(input)==124,""); + _synctex_data_set_line(input,421); + SYNCTEX_TEST_BODY(TC, _synctex_data_line(input)==421,""); + _synctex_data_set_line(input,214); + SYNCTEX_TEST_BODY(TC, _synctex_data_line(input)==214,""); + _synctex_data_set_line(input,214); + SYNCTEX_TEST_BODY(TC, _synctex_data_line(input)==214,""); + _synctex_input_copy_name(input,"214"); + SYNCTEX_TEST_BODY(TC, 0==memcmp(_synctex_data_name(input),"214",4),""); + _synctex_input_copy_name(input,"421421"); + + SYNCTEX_TEST_BODY(TC, + 0==memcmp(_synctex_data_name(input), + "421421", + 4), + ""); + synctex_node_free(input); + return TC; +} +int synctex_test_proxy(synctex_scanner_p scanner) { + int TC = 0; + synctex_node_p proxy = synctex_node_new(scanner,synctex_node_type_proxy); + synctex_node_p target = synctex_node_new(scanner,synctex_node_type_rule); + _synctex_tree_set_target(proxy,target); + _synctex_data_set_tag(target,421); + SYNCTEX_TEST_BODY(TC, _synctex_data_tag(target)==421,""); + SYNCTEX_TEST_BODY(TC, synctex_node_tag(target)==421,""); + SYNCTEX_TEST_BODY(TC, synctex_node_tag(proxy)==421,""); + synctex_node_free(proxy); + synctex_node_free(target); + return TC; +} +int synctex_test_handle(synctex_scanner_p scanner) { + int TC = 0; + synctex_node_p handle = synctex_node_new(scanner,synctex_node_type_handle); + synctex_node_p proxy = synctex_node_new(scanner, synctex_node_type_proxy); + synctex_node_p target = synctex_node_new(scanner,synctex_node_type_rule); + _synctex_tree_set_target(handle,target); + _synctex_data_set_tag(target,421); + SYNCTEX_TEST_BODY(TC, _synctex_data_tag(target)==421,""); + SYNCTEX_TEST_BODY(TC, synctex_node_tag(target)==421,""); + SYNCTEX_TEST_BODY(TC, synctex_node_tag(handle)==421,""); + _synctex_data_set_line(target,214); + SYNCTEX_TEST_BODY(TC, _synctex_data_line(target)==214,""); + SYNCTEX_TEST_BODY(TC, synctex_node_line(target)==214,""); + SYNCTEX_TEST_BODY(TC, synctex_node_line(handle)==214,""); + _synctex_data_set_column(target,142); + SYNCTEX_TEST_BODY(TC, _synctex_data_column(target)==142,""); + SYNCTEX_TEST_BODY(TC, synctex_node_column(target)==142,""); + SYNCTEX_TEST_BODY(TC, synctex_node_column(handle)==142,""); + _synctex_tree_set_target(proxy,target); + _synctex_tree_set_target(handle,proxy); + _synctex_data_set_tag(target,412); + SYNCTEX_TEST_BODY(TC, _synctex_data_tag(target)==412,""); + SYNCTEX_TEST_BODY(TC, synctex_node_tag(target)==412,""); + SYNCTEX_TEST_BODY(TC, synctex_node_tag(handle)==412,""); + _synctex_data_set_line(target,124); + SYNCTEX_TEST_BODY(TC, _synctex_data_line(target)==124,""); + SYNCTEX_TEST_BODY(TC, synctex_node_line(target)==124,""); + SYNCTEX_TEST_BODY(TC, synctex_node_line(handle)==124,""); + _synctex_data_set_column(target,241); + SYNCTEX_TEST_BODY(TC, _synctex_data_column(target)==241,""); + SYNCTEX_TEST_BODY(TC, synctex_node_column(target)==241,""); + SYNCTEX_TEST_BODY(TC, synctex_node_column(handle)==241,""); + synctex_node_free(handle); + synctex_node_free(proxy); + synctex_node_free(target); + return TC; +} +int synctex_test_setup_scanner_input(synctex_scanner_p scanner) { + int TC = 0; + synctex_node_p input = synctex_node_new(scanner,synctex_node_type_input); + _synctex_data_set_tag(input,4); + _synctex_input_copy_name(input,"21"); + _synctex_data_set_line(input,421); + synctex_node_free(scanner->input); + scanner->input = input; + SYNCTEX_TEST_BODY(TC, _synctex_data_tag(input)==4,""); + SYNCTEX_TEST_BODY(TC, strcmp(_synctex_data_name(input),"21")==0,""); + SYNCTEX_TEST_BODY(TC, _synctex_data_line(input)==421,""); + return TC; +} +int synctex_test_setup_nodes(synctex_scanner_p scanner, synctex_node_r nodes) { + int TC = 0; + int n; + for (n=0;n<synctex_node_number_of_types;++n) { + nodes[n] = synctex_node_new(scanner,n); + SYNCTEX_TEST_BODY(TC, nodes[n]!=NULL,""); + } + return TC; +} +int synctex_test_teardown_nodes(synctex_scanner_p scanner, synctex_node_r nodes) { + int n; + for (n=0;n<synctex_node_number_of_types;++n) { + synctex_node_free(nodes[n]); + nodes[n]=NULL; + } + return 1; +} +int synctex_test_tree(synctex_scanner_p scanner) { + int TC = 0; + synctex_node_p nodes1[synctex_node_number_of_types]; + synctex_node_p nodes2[synctex_node_number_of_types]; + synctex_node_p nodes3[synctex_node_number_of_types]; + int i,j; + TC += synctex_test_setup_nodes(scanner,nodes1); + TC += synctex_test_setup_nodes(scanner,nodes2); + TC += synctex_test_setup_nodes(scanner,nodes3); + /* Every node has a sibling */ + for (i=0;i<synctex_node_number_of_types;++i) { + for (j=0;j<synctex_node_number_of_types;++j) { + _synctex_tree_set_sibling(nodes1[i],nodes2[i]); + SYNCTEX_TEST_BODY(TC, nodes2[i]==synctex_node_sibling(nodes1[i]),""); + } + } + synctex_test_teardown_nodes(scanner,nodes3); + synctex_test_teardown_nodes(scanner,nodes2); + synctex_test_teardown_nodes(scanner,nodes1); + return TC; +} +int synctex_test_page(synctex_scanner_p scanner) { + int TC = synctex_test_setup_scanner_sheets_421(scanner); + synctex_node_p sheet = scanner->sheet; + synctex_node_p node = synctex_node_new(scanner,synctex_node_type_rule); + _synctex_data_set_tag(node,4); + _synctex_data_set_line(node,21); + synctex_node_free(_synctex_node_set_child(sheet,node)); + SYNCTEX_TEST_BODY(TC, synctex_node_page(node)==synctex_node_page(sheet),""); + return TC; +} +int synctex_test_display_query(synctex_scanner_p scanner) { + int TC = synctex_test_setup_scanner_sheets_421(scanner); + synctex_node_p sheet = scanner->sheet; + synctex_node_p node = synctex_node_new(scanner,synctex_node_type_rule); + _synctex_data_set_tag(node,4); + _synctex_data_set_line(node,21); + synctex_node_free(_synctex_node_set_child(sheet,node)); + SYNCTEX_TEST_BODY(TC, node==synctex_node_child(sheet),""); + __synctex_node_make_friend_tlc(node); + SYNCTEX_TEST_BODY(TC, _synctex_scanner_friend(scanner, 25)==node,""); + sheet = __synctex_tree_sibling(sheet); + node = synctex_node_new(scanner,synctex_node_type_rule); + _synctex_data_set_tag(node,4); + _synctex_data_set_line(node,21); + synctex_node_free(_synctex_node_set_child(sheet,node)); + SYNCTEX_TEST_BODY(TC, node==synctex_node_child(sheet),""); + __synctex_node_make_friend_tlc(node); + SYNCTEX_TEST_BODY(TC, _synctex_scanner_friend(scanner, 25)==node,""); + sheet = __synctex_tree_sibling(sheet); + node = synctex_node_new(scanner,synctex_node_type_rule); + _synctex_data_set_tag(node,4); + _synctex_data_set_line(node,21); + synctex_node_free(_synctex_node_set_child(sheet,node)); + SYNCTEX_TEST_BODY(TC, node==synctex_node_child(sheet),""); + __synctex_node_make_friend_tlc(node); + SYNCTEX_TEST_BODY(TC, (_synctex_scanner_friend(scanner, 25)==node),""); + synctex_test_setup_scanner_input(scanner); + scanner->flags.has_parsed = synctex_YES; +#if 1 + SYNCTEX_TEST_BODY(TC, (synctex_display_query(scanner,"21",21,4,-1)==3),""); +#endif + return TC; +} +typedef struct { + int s; /* status */ + char n[25]; /* name */ +} synctex_test_sn_s; + +synctex_test_sn_s synctex_test_tmp_sn(char * content) { + synctex_test_sn_s sn = {0, "/tmp/test.XXXXXX.synctex"}; + FILE *sfp; + int fd = mkstemps(sn.n,8); + if (fd < 0) { + fprintf(stderr, "%s: %s\n", sn.n, strerror(errno)); + sn.s = -1; + return sn; + } + if ((sfp = fdopen(fd, "w+")) == NULL) { + unlink(sn.n); + close(fd); + fprintf(stderr, "%s: %s\n", sn.n, strerror(errno)); + sn.s = -2; + return sn; + } + sn.s = fputs(content,sfp); + printf("temp:%s\n%i\n",sn.n,sn.s); + fclose(sfp); + if (sn.s==0) { + sn.s = -2; + unlink(sn.n); + } + return sn; +} +int synctex_test_sheet_1() { + int TC = 0; + char * content = + "SyncTeX Version:1 \n" /*00-19*/ + "Input:1:./1.tex \n" /*20-39*/ + "Output:pdf \n" /*40-59*/ + "Magnification:100000000 \n" /*60-89*/ + "Unit:1 \n" /*90-99*/ + "X Offset:0 \n" /*00-19*/ + "Y Offset:0 \n" /*20-39*/ + "Content: \n" /*40-49*/ + "{1 \n" /*50-59*/ + "[1,10:20,350:330,330,0 \n" /*60-89*/ + "] \n" /*90-99*/ + "} \n" /*00-09*/ + "Postamble:\n"; + synctex_test_sn_s sn = synctex_test_tmp_sn(content); + if (sn.s>0) { + synctex_scanner_p scanner = synctex_scanner_new_with_output_file(sn.n, NULL, synctex_YES); + synctex_node_p node = synctex_scanner_handle(scanner); + printf("Created nodes:\n"); + while (node) { + printf("%s\n",_synctex_node_abstract(node)); + node = synctex_node_next(node); + } + synctex_scanner_free(scanner); + unlink(sn.n); + } else { + ++TC; + } + return TC; +} +int synctex_test_sheet_2() { + int TC = 0; + char * content = + "SyncTeX Version:1 \n" /*00-19*/ + "Input:1:./1.tex \n" /*20-39*/ + "Output:pdf \n" /*40-59*/ + "Magnification:100000000 \n" /*60-89*/ + "Unit:1 \n" /*90-99*/ + "X Offset:0 \n" /*00-19*/ + "Y Offset:0 \n" /*20-39*/ + "Content: \n" /*40-49*/ + "{1 \n" /*50-59*/ + "(1,10:20,350:330,330,0 \n" /*60-89*/ + ") \n" /*90-99*/ + "} \n" /*00-09*/ + "Postamble:\n"; + synctex_test_sn_s sn = synctex_test_tmp_sn(content); + if (sn.s>0) { + synctex_scanner_p scanner = synctex_scanner_new_with_output_file(sn.n, NULL, synctex_YES); + synctex_node_p node = synctex_scanner_handle(scanner); + printf("Created nodes:\n"); + while (node) { + printf("%s\n",_synctex_node_abstract(node)); + node = _synctex_node_next(node); + } + TC += synctex_scanner_free(scanner); + unlink(sn.n); + } else { + ++TC; + } + return TC; +} +int synctex_test_charindex() { + int TC = 0; + char * content = + "SyncTeX Version:1 \n" /*00-19*/ + "Input:1:./1.tex \n" /*20-39*/ + "Output:pdf \n" /*40-59*/ + "Magnification:100000000 \n" /*60-89*/ + "Unit:1 \n" /*90-99*/ + "X Offset:0 \n" /*00-19*/ + "Y Offset:0 \n" /*20-39*/ + "Content: \n" /*40-49*/ + "{1 \n" /*50-59*/ + "[1,10:20,350:330,330,0 \n" /*60-89*/ + "(1,58:20,100:250,10,5 \n" /*90-119*/ + "f1000:50,100 \n" /*20-39*/ + ") \n" /*40-49*/ + "] \n" /*50-59*/ + "} \n" /*60-69*/ + "Postamble:\n"; + synctex_test_sn_s sn = synctex_test_tmp_sn(content); + if (sn.s>0) { + synctex_scanner_p scanner = synctex_scanner_new_with_output_file(sn.n, NULL, synctex_YES); + synctex_node_p node = synctex_scanner_handle(scanner); + printf("Created nodes:\n"); + while (node) { + printf("%s\n",_synctex_node_abstract(node)); + node = synctex_node_next(node); + } + TC += synctex_scanner_free(scanner); + unlink(sn.n); + } else { + ++TC; + } + return TC; +} +int synctex_test_form() { + int TC = 0; + char * content = + "SyncTeX Version:1 \n" /*00-19*/ + "Input:1:./1.tex \n" /*20-39*/ + "Output:pdf \n" /*40-59*/ + "Magnification:100000000 \n" /*60-89*/ + "Unit:1 \n" /*90-99*/ + "X Offset:0 \n" /*00-19*/ + "Y Offset:0 \n" /*20-39*/ + "Content: \n" /*40-49*/ + "{1 \n" /*50-59*/ + "[1,10:20,350:330,330,0 \n" /*60-89*/ + "(1,58:20,100:250,10,5 \n" /*90-119*/ + "f1000:50,100 \n" /*20-39*/ + ") \n" /*40-49*/ + "] \n" /*50-59*/ + "} \n" /*60-69*/ + "<1000 \n" /*70-79*/ + "(1,63:0,0:100,8,3 \n" /*80-99*/ + ") \n" /*00-09*/ + "> \n" /*10-19*/ + "Postamble:\n"; + synctex_test_sn_s sn = synctex_test_tmp_sn(content); + if (sn.s>0) { + synctex_scanner_p scanner = synctex_scanner_new_with_output_file(sn.n, NULL, synctex_YES); + synctex_node_p node = synctex_scanner_handle(scanner); + while (node) { + printf("%s\n",_synctex_node_abstract(node)); + node = _synctex_node_next(node); + } + TC += synctex_scanner_free(scanner); + unlink(sn.n); + } else { + ++TC; + } + return TC; +} +#endif diff --git a/elpa/pdf-tools-20211110.513/build/server/synctex_parser.h b/elpa/pdf-tools-20211110.513/build/server/synctex_parser.h @@ -0,0 +1,429 @@ +/* + Copyright (c) 2008-2017 jerome DOT laurens AT u-bourgogne DOT fr + + This file is part of the __SyncTeX__ package. + + [//]: # (Latest Revision: Fri Jul 14 16:20:41 UTC 2017) + [//]: # (Version: 1.21) + + See `synctex_parser_readme.md` for more details + + ## License + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE + + Except as contained in this notice, the name of the copyright holder + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization from the copyright holder. + + ## Acknowledgments: + + The author received useful remarks from the __pdfTeX__ developers, especially Hahn The Thanh, + and significant help from __XeTeX__ developer Jonathan Kew. + + ## Nota Bene: + + If you include or use a significant part of the __SyncTeX__ package into a software, + I would appreciate to be listed as contributor and see "__SyncTeX__" highlighted. +*/ + +#ifndef __SYNCTEX_PARSER__ +# define __SYNCTEX_PARSER__ + +#include "synctex_version.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /* The main synctex object is a scanner. + * Its implementation is considered private. + * The basic workflow is + * - create a "synctex scanner" with the contents of a file + * - perform actions on that scanner like + synctex_display_query or synctex_edit_query below. + * - perform actions on nodes returned by the scanner + * - free the scanner when the work is done + */ + typedef struct synctex_scanner_t synctex_scanner_s; + typedef synctex_scanner_s * synctex_scanner_p; + + /** + * This is the designated method to create + * a new synctex scanner object. + * - argument output: the pdf/dvi/xdv file associated + * to the synctex file. + * If necessary, it can be the tex file that + * originated the synctex file but this might cause + * problems if the \jobname has a custom value. + * Despite this method can accept a relative path + * in practice, you should only pass full paths. + * The path should be encoded by the underlying + * file system, assuming that it is based on + * 8 bits characters, including UTF8, + * not 16 bits nor 32 bits. + * The last file extension is removed and + * replaced by the proper extension, + * either synctex or synctex.gz. + * - argument build_directory: It is the directory where + * all the auxiliary stuff is created. + * If no synctex file is found in the same directory + * as the output file, then we try to find one in + * this build directory. + * It is the directory where all the auxiliary + * stuff is created. Sometimes, the synctex output + * file and the pdf, dvi or xdv files are not + * created in the same location. See MikTeX. + * This directory path can be NULL, + * it will be ignored then. + * It can be either absolute or relative to the + * directory of the output pdf (dvi or xdv) file. + * Please note that this new argument is provided + * as a convenience but should not be used. + * Available since version 1.5. + * - argument parse: In general, use 1. + * Use 0 only if you do not want to parse the + * content but just check for existence. + * Available since version 1.5 + * - return: a scanner. NULL is returned in case + * of an error or non existent file. + */ + synctex_scanner_p synctex_scanner_new_with_output_file(const char * output, const char * build_directory, int parse); + + /** + * Designated method to delete a synctex scanner object, + * including all its internal resources. + * Frees all the memory, you must call it when you are finished with the scanner. + * - argument scanner: a scanner. + * - returns: an integer used for testing purposes. + */ + int synctex_scanner_free(synctex_scanner_p scanner); + + /** + * Send this message to force the scanner to + * parse the contents of the synctex output file. + * Nothing is performed if the file was already parsed. + * In each query below, this message is sent, + * but if you need to access information more directly, + * you must ensure that the parsing did occur. + * Usage: + * if((my_scanner = synctex_scanner_parse(my_scanner))) { + * continue with my_scanner... + * } else { + * there was a problem + * } + * - returns: the argument on success. + * On failure, frees scanner and returns NULL. + */ + synctex_scanner_p synctex_scanner_parse(synctex_scanner_p scanner); + + /* synctex_node_p is the type for all synctex nodes. + * Its implementation is considered private. + * The synctex file is parsed into a tree of nodes, either sheet, form, boxes, math nodes... */ + + typedef struct synctex_node_t synctex_node_s; + typedef synctex_node_s * synctex_node_p; + + /* The main entry points. + * Given the file name, a line and a column number, synctex_display_query returns the number of nodes + * satisfying the contrain. Use code like + * + * if(synctex_display_query(scanner,name,line,column,page_hint)>0) { + * synctex_node_p node; + * while((node = synctex_scanner_next_result(scanner))) { + * // do something with node + * ... + * } + * } + * + * Please notice that since version 1.19, + * there is a new argument page_hint. + * The results in pages closer to page_hint are given first. + * For example, one can + * - highlight each resulting node in the output, using synctex_node_visible_h and synctex_node_visible_v + * - highlight all the rectangles enclosing those nodes, using synctex_node_box_visible_... functions + * - highlight just the character using that information + * + * Given the page and the position in the page, synctex_edit_query returns the number of nodes + * satisfying the contrain. Use code like + * + * if(synctex_edit_query(scanner,page,h,v)>0) { + * synctex_node_p node; + * while(node = synctex_scanner_next_result(scanner)) { + * // do something with node + * ... + * } + * } + * + * For example, one can + * - highlight each resulting line in the input, + * - highlight just the character using that information + * + * page is 1 based + * h and v are coordinates in 72 dpi unit, relative to the top left corner of the page. + * If you make a new query, the result of the previous one is discarded. If you need to make more than one query + * in parallel, use the iterator API exposed in + * the synctex_parser_private.h header. + * If one of this function returns a negative integer, + * it means that an error occurred. + * + * Both methods are conservative, in the sense that matching is weak. + * If the exact column number is not found, there will be an answer with the whole line. + * + * Sumatra-PDF, Skim, iTeXMac2, TeXShop and Texworks are examples of open source software that use this library. + * You can browse their code for a concrete implementation. + */ + typedef long synctex_status_t; + /* The page_hint argument is used to resolve ambiguities. + * Whenever, different matches occur, the ones closest + * to the page will be given first. Pass a negative number + * when in doubt. Using pdf forms may lead to ambiguities. + */ + synctex_status_t synctex_display_query(synctex_scanner_p scanner,const char * name,int line,int column, int page_hint); + synctex_status_t synctex_edit_query(synctex_scanner_p scanner,int page,float h,float v); + synctex_node_p synctex_scanner_next_result(synctex_scanner_p scanner); + synctex_status_t synctex_scanner_reset_result(synctex_scanner_p scanner); + + /** + * The horizontal and vertical location, + * the width, height and depth of a box enclosing node. + * All dimensions are given in page coordinates + * as opposite to TeX coordinates. + * The origin is at the top left corner of the page. + * Code example for Qt5: + * (from TeXworks source TWSynchronize.cpp) + * QRectF nodeRect(synctex_node_box_visible_h(node), + * synctex_node_box_visible_v(node) - + * synctex_node_box_visible_height(node), + * synctex_node_box_visible_width(node), + * synctex_node_box_visible_height(node) + + * synctex_node_box_visible_depth(node)); + * Code example for Cocoa: + * NSRect bounds = [pdfPage + * boundsForBox:kPDFDisplayBoxMediaBox]; + * NSRect nodeRect = NSMakeRect( + * synctex_node_box_visible_h(node), + * NSMaxY(bounds)-synctex_node_box_visible_v(node) + + * synctex_node_box_visible_height(node), + * synctex_node_box_visible_width(node), + * synctex_node_box_visible_height(node) + + * synctex_node_box_visible_depth(node) + * ); + * The visible dimensions are bigger than real ones + * to compensate 0 width boxes or nodes intentionnaly + * put outside the box (using \kern for example). + * - parameter node: a node. + * - returns: a float. + * - author: JL + */ + float synctex_node_box_visible_h(synctex_node_p node); + float synctex_node_box_visible_v(synctex_node_p node); + float synctex_node_box_visible_width(synctex_node_p node); + float synctex_node_box_visible_height(synctex_node_p node); + float synctex_node_box_visible_depth(synctex_node_p node); + + /** + * For quite all nodes, horizontal and vertical coordinates, and width. + * All dimensions are given in page coordinates + * as opposite to TeX coordinates. + * The origin is at the top left corner of the page. + * The visible dimensions are bigger than real ones + * to compensate 0 width boxes or nodes intentionnaly + * put outside the box (using \kern for example). + * All nodes have coordinates, but all nodes don't + * have non null size. For example, math nodes + * have no width according to TeX, and in that case + * synctex_node_visible_width simply returns 0. + * The same holds for kern nodes that do not have + * height nor depth, etc... + */ + float synctex_node_visible_h(synctex_node_p node); + float synctex_node_visible_v(synctex_node_p node); + float synctex_node_visible_width(synctex_node_p node); + float synctex_node_visible_height(synctex_node_p node); + float synctex_node_visible_depth(synctex_node_p node); + + /** + * Given a node, access to its tag, line and column. + * The line and column numbers are 1 based. + * The latter is not yet fully supported in TeX, + * the default implementation returns 0 + * which means the whole line. + * synctex_node_get_name returns the path of the + * TeX source file that was used to create the node. + * When the tag is known, the scanner of the node + * will also give that same file name, see + * synctex_scanner_get_name below. + * For an hbox node, the mean line is the mean + * of all the lines of the child nodes. + * Sometimes, when synchronization form pdf to source + * fails with the line, one should try with the + * mean line. + */ + int synctex_node_tag(synctex_node_p node); + int synctex_node_line(synctex_node_p node); + int synctex_node_mean_line(synctex_node_p node); + int synctex_node_column(synctex_node_p node); + const char * synctex_node_get_name(synctex_node_p node); + + /** + This is the page where the node appears. + * This is a 1 based index as given by TeX. + */ + int synctex_node_page(synctex_node_p node); + + /** + * Display all the information contained in the scanner. + * If the records are too numerous, only the first ones are displayed. + * This is mainly for informational purpose to help developers. + */ + void synctex_scanner_display(synctex_scanner_p scanner); + + /* Managing the input file names. + * Given a tag, synctex_scanner_get_name will return the corresponding file name. + * Conversely, given a file name, synctex_scanner_get_tag will return, the corresponding tag. + * The file name must be the very same as understood by TeX. + * For example, if you \input myDir/foo.tex, the file name is myDir/foo.tex. + * No automatic path expansion is performed. + * Finally, synctex_scanner_input is the first input node of the scanner. + * To browse all the input node, use a loop like + * ... + * synctex_node_p = input_node; + * ... + * if((input_node = synctex_scanner_input(scanner))) { + * do { + * blah + * } while((input_node=synctex_node_sibling(input_node))); + * } + * + * The output is the name that was used to create the scanner. + * The synctex is the real name of the synctex file, + * it was obtained from output by setting the proper file extension. + */ + const char * synctex_scanner_get_name(synctex_scanner_p scanner,int tag); + + int synctex_scanner_get_tag(synctex_scanner_p scanner,const char * name); + + synctex_node_p synctex_scanner_input(synctex_scanner_p scanner); + synctex_node_p synctex_scanner_input_with_tag(synctex_scanner_p scanner,int tag); + const char * synctex_scanner_get_output(synctex_scanner_p scanner); + const char * synctex_scanner_get_synctex(synctex_scanner_p scanner); + + /* The x and y offset of the origin in TeX coordinates. The magnification + These are used by pdf viewers that want to display the real box size. + For example, getting the horizontal coordinates of a node would require + synctex_node_box_h(node)*synctex_scanner_magnification(scanner)+synctex_scanner_x_offset(scanner) + Getting its TeX width would simply require + synctex_node_box_width(node)*synctex_scanner_magnification(scanner) + but direct methods are available for that below. + */ + int synctex_scanner_x_offset(synctex_scanner_p scanner); + int synctex_scanner_y_offset(synctex_scanner_p scanner); + float synctex_scanner_magnification(synctex_scanner_p scanner); + + /** + * ## Browsing the nodes + * parent, child and sibling are standard names for tree nodes. + * The parent is one level higher, + * the child is one level deeper, + * and the sibling is at the same level. + * A node and its sibling have the same parent. + * A node is the parent of its children. + * A node is either the child of its parent, + * or belongs to the sibling chain of its parent's child. + * The sheet or form of a node is the topmost ancestor, + * it is of type sheet or form. + * The next node is either the child, the sibling or the parent's sibling, + * unless the parent is a sheet, a form or NULL. + * This allows to navigate through all the nodes of a given sheet node: + * + * synctex_node_p node = sheet; + * while((node = synctex_node_next(node))) { + * // do something with node + * } + * + * With synctex_sheet_content and synctex_form_content, + * you can retrieve the sheet node given the page + * or form tag. + * The page is 1 based, according to TeX standards. + * Conversely synctex_node_parent_sheet or + * synctex_node_parent_form allows to retrieve + * the sheet or the form containing a given node. + * Notice that a node is not contained in a sheet + * and a form at the same time. + * Some nodes are not contained in either (handles). + */ + + synctex_node_p synctex_node_parent(synctex_node_p node); + synctex_node_p synctex_node_parent_sheet(synctex_node_p node); + synctex_node_p synctex_node_parent_form(synctex_node_p node); + synctex_node_p synctex_node_child(synctex_node_p node); + synctex_node_p synctex_node_last_child(synctex_node_p node); + synctex_node_p synctex_node_sibling(synctex_node_p node); + synctex_node_p synctex_node_last_sibling(synctex_node_p node); + synctex_node_p synctex_node_arg_sibling(synctex_node_p node); + synctex_node_p synctex_node_next(synctex_node_p node); + + /** + * Top level entry points. + * The scanner owns a list of sheet siblings and + * a list of form siblings. + * Sheets or forms have one child which is a box: + * theie contents. + * - argument page: 1 based sheet page number. + * - argument tag: 1 based form tag number. + */ + synctex_node_p synctex_sheet(synctex_scanner_p scanner,int page); + synctex_node_p synctex_sheet_content(synctex_scanner_p scanner,int page); + synctex_node_p synctex_form(synctex_scanner_p scanner,int tag); + synctex_node_p synctex_form_content(synctex_scanner_p scanner,int tag); + + /* This is primarily used for debugging purpose. + * The second one logs information for the node and recursively displays information for its next node */ + void synctex_node_log(synctex_node_p node); + void synctex_node_display(synctex_node_p node); + + /* For quite all nodes, horizontal, vertical coordinates, and width. + * These are expressed in TeX small points coordinates, with origin at the top left corner. + */ + int synctex_node_h(synctex_node_p node); + int synctex_node_v(synctex_node_p node); + int synctex_node_width(synctex_node_p node); + int synctex_node_height(synctex_node_p node); + int synctex_node_depth(synctex_node_p node); + + /* For all nodes, dimensions of the enclosing box. + * These are expressed in TeX small points coordinates, with origin at the top left corner. + * A box is enclosing itself. + */ + int synctex_node_box_h(synctex_node_p node); + int synctex_node_box_v(synctex_node_p node); + int synctex_node_box_width(synctex_node_p node); + int synctex_node_box_height(synctex_node_p node); + int synctex_node_box_depth(synctex_node_p node); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/elpa/pdf-tools-20211110.513/build/server/synctex_parser_advanced.h b/elpa/pdf-tools-20211110.513/build/server/synctex_parser_advanced.h @@ -0,0 +1,554 @@ +/* + Copyright (c) 2008-2017 jerome DOT laurens AT u-bourgogne DOT fr + + This file is part of the __SyncTeX__ package. + + [//]: # (Latest Revision: Sun Oct 15 15:09:55 UTC 2017) + [//]: # (Version: 1.21) + + See `synctex_parser_readme.md` for more details + + ## License + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE + + Except as contained in this notice, the name of the copyright holder + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization from the copyright holder. + */ + +#include "synctex_parser.h" +#include "synctex_parser_utils.h" + +#ifndef __SYNCTEX_PARSER_PRIVATE__ +# define __SYNCTEX_PARSER_PRIVATE__ + +#ifdef __cplusplus +extern "C" { +#endif + /* Reminder that the argument must not be NULL */ + typedef synctex_node_p synctex_non_null_node_p; + + /* Each node of the tree, except the scanner itself belongs to a class. + * The class object is just a struct declaring the owning scanner + * This is a pointer to the scanner as root of the tree. + * The type is used to identify the kind of node. + * The class declares pointers to a creator and a destructor method. + * The log and display fields are used to log and display the node. + * display will also display the child, sibling and parent sibling. + * parent, child and sibling are used to navigate the tree, + * from TeX box hierarchy point of view. + * The friend field points to a method which allows to navigate from friend to friend. + * A friend is a node with very close tag and line numbers. + * Finally, the info field point to a method giving the private node info offset. + */ + + /** + * These are the masks for the synctex node types. + * int's are 32 bits at least. + */ + enum { + synctex_shift_root, + synctex_shift_no_root, + synctex_shift_void, + synctex_shift_no_void, + synctex_shift_box, + synctex_shift_no_box, + synctex_shift_proxy, + synctex_shift_no_proxy, + synctex_shift_h, + synctex_shift_v + }; + enum { + synctex_mask_root = 1, + synctex_mask_no_root = synctex_mask_root<<1, + synctex_mask_void = synctex_mask_no_root<<1, + synctex_mask_no_void = synctex_mask_void<<1, + synctex_mask_box = synctex_mask_no_void<<1, + synctex_mask_no_box = synctex_mask_box<<1, + synctex_mask_proxy = synctex_mask_no_box<<1, + synctex_mask_no_proxy = synctex_mask_proxy<<1, + synctex_mask_h = synctex_mask_no_proxy<<1, + synctex_mask_v = synctex_mask_h<<1, + }; + enum { + synctex_mask_non_void_hbox = synctex_mask_no_void + | synctex_mask_box + | synctex_mask_h, + synctex_mask_non_void_vbox = synctex_mask_no_void + | synctex_mask_box + | synctex_mask_v + }; + typedef enum { + synctex_node_mask_sf = + synctex_mask_root + |synctex_mask_no_void + |synctex_mask_no_box + |synctex_mask_no_proxy, + synctex_node_mask_vbox = + synctex_mask_no_root + |synctex_mask_no_void + |synctex_mask_box + |synctex_mask_no_proxy + |synctex_mask_v, + synctex_node_mask_hbox = + synctex_mask_no_root + |synctex_mask_no_void + |synctex_mask_box + |synctex_mask_no_proxy + |synctex_mask_h, + synctex_node_mask_void_vbox = + synctex_mask_no_root + |synctex_mask_void + |synctex_mask_box + |synctex_mask_no_proxy + |synctex_mask_v, + synctex_node_mask_void_hbox = + synctex_mask_no_root + |synctex_mask_void + |synctex_mask_box + |synctex_mask_no_proxy + |synctex_mask_h, + synctex_node_mask_vbox_proxy = + synctex_mask_no_root + |synctex_mask_no_void + |synctex_mask_box + |synctex_mask_proxy + |synctex_mask_v, + synctex_node_mask_hbox_proxy = + synctex_mask_no_root + |synctex_mask_no_void + |synctex_mask_box + |synctex_mask_proxy + |synctex_mask_h, + synctex_node_mask_nvnn = + synctex_mask_no_root + |synctex_mask_void + |synctex_mask_no_box + |synctex_mask_no_proxy, + synctex_node_mask_input = + synctex_mask_root + |synctex_mask_void + |synctex_mask_no_box + |synctex_mask_no_proxy, + synctex_node_mask_proxy = + synctex_mask_no_root + |synctex_mask_void + |synctex_mask_no_box + |synctex_mask_proxy + } synctex_node_mask_t; + + enum { + /* input */ + synctex_tree_sibling_idx = 0, + synctex_tree_s_input_max = 1, + /* All */ + synctex_tree_s_parent_idx = 1, + synctex_tree_sp_child_idx = 2, + synctex_tree_spc_friend_idx = 3, + synctex_tree_spcf_last_idx = 4, + synctex_tree_spcfl_vbox_max = 5, + /* hbox supplement */ + synctex_tree_spcfl_next_hbox_idx = 5, + synctex_tree_spcfln_hbox_max = 6, + /* hbox proxy supplement */ + synctex_tree_spcfln_target_idx = 6, + synctex_tree_spcflnt_proxy_hbox_max = 7, + /* vbox proxy supplement */ + synctex_tree_spcfl_target_idx = 5, + synctex_tree_spcflt_proxy_vbox_max = 6, + /* spf supplement*/ + synctex_tree_sp_friend_idx = 2, + synctex_tree_spf_max = 3, + /* box boundary supplement */ + synctex_tree_spf_arg_sibling_idx = 3, + synctex_tree_spfa_max = 4, + /* proxy supplement */ + synctex_tree_spf_target_idx = 3, + synctex_tree_spft_proxy_max = 4, + /* last proxy supplement */ + synctex_tree_spfa_target_idx = 4, + synctex_tree_spfat_proxy_last_max = 5, + /* sheet supplement */ + synctex_tree_s_child_idx = 1, + synctex_tree_sc_next_hbox_idx = 2, + synctex_tree_scn_sheet_max = 3, + /* form supplement */ + synctex_tree_sc_target_idx = 2, + synctex_tree_sct_form_max = 3, + /* spct */ + synctex_tree_spc_target_idx = 3, + synctex_tree_spct_handle_max = 4, + }; + + enum { + /* input */ + synctex_data_input_tag_idx = 0, + synctex_data_input_line_idx = 1, + synctex_data_input_name_idx = 2, + synctex_data_input_tln_max = 3, + /* sheet */ + synctex_data_sheet_page_idx = 0, + synctex_data_p_sheet_max = 1, + /* form */ + synctex_data_form_tag_idx = 0, + synctex_data_t_form_max = 1, + /* tlchv */ + synctex_data_tag_idx = 0, + synctex_data_line_idx = 1, + synctex_data_column_idx = 2, + synctex_data_h_idx = 3, + synctex_data_v_idx = 4, + synctex_data_tlchv_max = 5, + /* tlchvw */ + synctex_data_width_idx = 5, + synctex_data_tlchvw_max = 6, + /* box */ + synctex_data_height_idx = 6, + synctex_data_depth_idx = 7, + synctex_data_box_max = 8, + /* hbox supplement */ + synctex_data_mean_line_idx = 8, + synctex_data_weight_idx = 9, + synctex_data_h_V_idx = 10, + synctex_data_v_V_idx = 11, + synctex_data_width_V_idx = 12, + synctex_data_height_V_idx = 13, + synctex_data_depth_V_idx = 14, + synctex_data_hbox_max = 15, + /* ref */ + synctex_data_ref_tag_idx = 0, + synctex_data_ref_h_idx = 1, + synctex_data_ref_v_idx = 2, + synctex_data_ref_thv_max = 3, + /* proxy */ + synctex_data_proxy_h_idx = 0, + synctex_data_proxy_v_idx = 1, + synctex_data_proxy_hv_max = 2, + /* handle */ + synctex_data_handle_w_idx = 0, + synctex_data_handle_w_max = 1, + }; + + /* each synctex node has a class */ + typedef struct synctex_class_t synctex_class_s; + typedef synctex_class_s * synctex_class_p; + + + /* synctex_node_p is a pointer to a node + * synctex_node_s is the target of the synctex_node_p pointer + * It is a pseudo object oriented program. + * class is a pointer to the class object the node belongs to. + * implementation is meant to contain the private data of the node + * basically, there are 2 kinds of information: navigation information and + * synctex information. Both will depend on the type of the node, + * thus different nodes will have different private data. + * There is no inheritancy overhead. + */ + typedef union { + synctex_node_p as_node; + int as_integer; + char * as_string; + void * as_pointer; + } synctex_data_u; + typedef synctex_data_u * synctex_data_p; + +# if defined(SYNCTEX_USE_CHARINDEX) + typedef unsigned int synctex_charindex_t; + synctex_charindex_t synctex_node_charindex(synctex_node_p node); + typedef synctex_charindex_t synctex_lineindex_t; + synctex_lineindex_t synctex_node_lineindex(synctex_node_p node); + synctex_node_p synctex_scanner_handle(synctex_scanner_p scanner); +# define SYNCTEX_DECLARE_CHARINDEX \ + synctex_charindex_t char_index;\ + synctex_lineindex_t line_index; +# define SYNCTEX_DECLARE_CHAR_OFFSET \ + synctex_charindex_t charindex_offset; +# else +# define SYNCTEX_DECLARE_CHARINDEX +# define SYNCTEX_DECLARE_CHAR_OFFSET +# endif + struct synctex_node_t { + SYNCTEX_DECLARE_CHARINDEX + synctex_class_p class_; +#ifdef DEBUG + synctex_data_u data[22]; +#else + synctex_data_u data[1]; +#endif + }; + + typedef synctex_node_p * synctex_node_r; + + typedef struct { + int h; + int v; + } synctex_point_s; + + typedef synctex_point_s * synctex_point_p; + + typedef struct { + synctex_point_s min; /* top left */ + synctex_point_s max; /* bottom right */ + } synctex_box_s; + + typedef synctex_box_s * synctex_box_p; + /** + * These are the types of the synctex nodes. + * No need to use them but the compiler needs them here. + * There are 3 kinds of nodes. + * - primary nodes + * - proxies + * - handles + * Primary nodes are created at parse time + * of the synctex file. + * Proxies are used to support pdf forms. + * The ref primary nodes are replaced by a tree + * of proxy nodes which duplicate the tree of primary + * nodes available in the referred form. + * Roughly speaking, the primary nodes of the form + * know what to display, the proxy nodes know where. + * Handles are used in queries. They point to either + * primary nodes or proxies. + */ + typedef enum { + synctex_node_type_none = 0, + synctex_node_type_input, + synctex_node_type_sheet, + synctex_node_type_form, + synctex_node_type_ref, + synctex_node_type_vbox, + synctex_node_type_void_vbox, + synctex_node_type_hbox, + synctex_node_type_void_hbox, + synctex_node_type_kern, + synctex_node_type_glue, + synctex_node_type_rule, + synctex_node_type_math, + synctex_node_type_boundary, + synctex_node_type_box_bdry, + synctex_node_type_proxy, + synctex_node_type_proxy_last, + synctex_node_type_proxy_vbox, + synctex_node_type_proxy_hbox, + synctex_node_type_handle, + synctex_node_number_of_types + } synctex_node_type_t; + /* synctex_node_type gives the type of a given node, + * synctex_node_isa gives the same information as a human readable text. */ + synctex_node_type_t synctex_node_type(synctex_node_p node); + const char * synctex_node_isa(synctex_node_p node); + + synctex_node_type_t synctex_node_target_type(synctex_node_p node); + + synctex_node_type_t synctex_node_type(synctex_node_p node); + const char * synctex_node_isa(synctex_node_p node); + + void synctex_node_log(synctex_node_p node); + void synctex_node_display(synctex_node_p node); + + /* Given a node, access to the location in the synctex file where it is defined. + */ + + int synctex_node_form_tag(synctex_node_p node); + + int synctex_node_weight(synctex_node_p node); + int synctex_node_child_count(synctex_node_p node); + + int synctex_node_h(synctex_node_p node); + int synctex_node_v(synctex_node_p node); + int synctex_node_width(synctex_node_p node); + + int synctex_node_box_h(synctex_node_p node); + int synctex_node_box_v(synctex_node_p node); + int synctex_node_box_width(synctex_node_p node); + int synctex_node_box_height(synctex_node_p node); + int synctex_node_box_depth(synctex_node_p node); + + int synctex_node_hbox_h(synctex_node_p node); + int synctex_node_hbox_v(synctex_node_p node); + int synctex_node_hbox_width(synctex_node_p node); + int synctex_node_hbox_height(synctex_node_p node); + int synctex_node_hbox_depth(synctex_node_p node); + + synctex_scanner_p synctex_scanner_new(void); + synctex_node_p synctex_node_new(synctex_scanner_p scanner,synctex_node_type_t type); + + /** + * Scanner display switcher getter. + * If the switcher is 0, synctex_node_display is disabled. + * If the switcher is <0, synctex_node_display has no limit. + * If the switcher is >0, only the first switcher (as number) nodes are displayed. + * - parameter: a scanner + * - returns: an integer + */ + int synctex_scanner_display_switcher(synctex_scanner_p scanR); + void synctex_scanner_set_display_switcher(synctex_scanner_p scanR, int switcher); + + /** + * Iterator is the structure used to traverse + * the answer to client queries. + * First answers are the best matches, according + * to criteria explained below. + * Next answers are not ordered. + * Objects are handles to nodes in the synctex node tree starting at scanner. + */ + typedef struct synctex_iterator_t synctex_iterator_s; + typedef synctex_iterator_s * synctex_iterator_p; + + /** + * Designated creator for a display query, id est, + * forward navigation from source to output. + * Returns NULL if the query has no answer. + * Code example: + * synctex_iterator_p iterator = NULL; + * if ((iterator = synctex_iterator_new_display(...)) { + * synctex_node_p node = NULL; + * while((node = synctex_iterator_next_result(iterator))) { + * do something with node... + * } + */ + synctex_iterator_p synctex_iterator_new_display(synctex_scanner_p scanner,const char * name,int line,int column, int page_hint); + /** + * Designated creator for an edit query, id est, + * backward navigation from output to source. + * Code example: + * synctex_iterator_p iterator = NULL; + * if ((iterator = synctex_iterator_new_edit(...)) { + * synctex_node_p node = NULL; + * while((node = synctex_iterator_next_result(iterator))) { + * do something with node... + * } + */ + synctex_iterator_p synctex_iterator_new_edit(synctex_scanner_p scanner,int page,float h,float v); + /** + * Free all the resources. + * - argument iterator: the object to free... + * You should free the iterator before the scanner + * owning the nodes it iterates with. + */ + void synctex_iterator_free(synctex_iterator_p iterator); + /** + * Whether the iterator actually points to an object. + * - argument iterator: the object to iterate on... + */ + synctex_bool_t synctex_iterator_has_next(synctex_iterator_p iterator); + /** + * Returns the pointed object and advance the cursor + * to the next object. Returns NULL and does nothing + * if the end has already been reached. + * - argument iterator: the object to iterate on... + */ + synctex_node_p synctex_iterator_next_result(synctex_iterator_p iterator); + /** + * Reset the cursor position to the first result. + * - argument iterator: the object to iterate on... + */ + int synctex_iterator_reset(synctex_iterator_p iterator); + /** + * The number of objects left for traversal. + * - argument iterator: the object to iterate on... + */ + int synctex_iterator_count(synctex_iterator_p iterator); + + /** + * The target of the node, either a handle or a proxy. + */ + synctex_node_p synctex_node_target(synctex_node_p node); + +#ifndef SYNCTEX_NO_UPDATER + /* The main synctex updater object. + * This object is used to append information to the synctex file. + * Its implementation is considered private. + * It is used by the synctex command line tool to take into account modifications + * that could occur while postprocessing files by dvipdf like filters. + */ + typedef struct synctex_updater_t synctex_updater_s; + typedef synctex_updater_s * synctex_updater_p; + + /* Designated initializer. + * Once you are done with your whole job, + * free the updater */ + synctex_updater_p synctex_updater_new_with_output_file(const char * output, const char * directory); + + /* Use the next functions to append records to the synctex file, + * no consistency tests made on the arguments */ + void synctex_updater_append_magnification(synctex_updater_p updater, char * magnification); + void synctex_updater_append_x_offset(synctex_updater_p updater, char * x_offset); + void synctex_updater_append_y_offset(synctex_updater_p updater, char * y_offset); + + /* You MUST free the updater, once everything is properly appended */ + void synctex_updater_free(synctex_updater_p updater); +#endif + +#if defined(SYNCTEX_DEBUG) +# include "assert.h" +# define SYNCTEX_ASSERT assert +#else +# define SYNCTEX_ASSERT(UNUSED) +#endif + +#if defined(SYNCTEX_TESTING) +#warning TESTING IS PROHIBITED +#if __clang__ +#define __PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \ +_Pragma("clang diagnostic push") \ +_Pragma("clang diagnostic ignored \"-Wformat-extra-args\"") + +#define __PRAGMA_POP_NO_EXTRA_ARG_WARNINGS _Pragma("clang diagnostic pop") +#else +#define __PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS +#define __PRAGMA_POP_NO_EXTRA_ARG_WARNINGS +#endif + +# define SYNCTEX_TEST_BODY(counter, condition, desc, ...) \ + do { \ + __PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \ + if (!(condition)) { \ + ++counter; \ + printf("**** Test failed: %s\nfile %s\nfunction %s\nline %i\n",#condition,__FILE__,__FUNCTION__,__LINE__); \ + printf((desc), ##__VA_ARGS__); \ + } \ + __PRAGMA_POP_NO_EXTRA_ARG_WARNINGS \ + } while(0) + +# define SYNCTEX_TEST_PARAMETER(counter, condition) SYNCTEX_TEST_BODY(counter, (condition), "Invalid parameter not satisfying: %s", #condition) + + int synctex_test_input(synctex_scanner_p scanner); + int synctex_test_proxy(synctex_scanner_p scanner); + int synctex_test_tree(synctex_scanner_p scanner); + int synctex_test_page(synctex_scanner_p scanner); + int synctex_test_handle(synctex_scanner_p scanner); + int synctex_test_display_query(synctex_scanner_p scanner); + int synctex_test_charindex(); + int synctex_test_sheet_1(); + int synctex_test_sheet_2(); + int synctex_test_sheet_3(); + int synctex_test_form(); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/elpa/pdf-tools-20211110.513/build/server/synctex_parser_local.h b/elpa/pdf-tools-20211110.513/build/server/synctex_parser_local.h @@ -0,0 +1,3 @@ +#include <stdio.h> +#define printf(fmt, args...) (fprintf (stderr, (fmt), ## args)) +#define SYNCTEX_INLINE diff --git a/elpa/pdf-tools-20211110.513/build/server/synctex_parser_readme.txt b/elpa/pdf-tools-20211110.513/build/server/synctex_parser_readme.txt @@ -0,0 +1,204 @@ +This file is part of the SyncTeX package. + +Please refer to synctex_parser_readme.md + +The Synchronization TeXnology named SyncTeX is a new feature +of recent TeX engines designed by Jerome Laurens. +It allows to synchronize between input and output, which means to +navigate from the source document to the typeset material and vice versa. +More information on http://itexmac2.sourceforge.net/SyncTeX.html + +This package is mainly for developers, it mainly contains the following files: + +synctex_parser_readme.txt +synctex_parser_version.txt +synctex_parser_utils.c +synctex_parser_utils.h +synctex_parser_local.h +synctex_parser_private.h +synctex_parser.h +synctex_parser.c + +The file you are reading contains more information about the SyncTeX parser history. + +In order to support SyncTeX in a viewer, it is sufficient to include +in the source the files synctex_parser.h and synctex_parser.c. +The synctex parser usage is described in synctex_parser.h header file. + +The other files are used by tex engines or by the synctex command line utility: + +ChangeLog +README.txt +am +man1 +man5 +synctex-common.h +synctex-convert.sh +synctex-e-mem.ch0 +synctex-e-mem.ch1 +synctex-e-rec.ch0 +synctex-e-rec.ch1 +synctex-etex.h +synctex-mem.ch0 +synctex-mem.ch1 +synctex-mem.ch2 +synctex-pdf-rec.ch2 +synctex-pdftex.h +synctex-rec.ch0 +synctex-rec.ch1 +synctex-rec.ch2 +synctex-tex.h +synctex-xe-mem.ch2 +synctex-xe-rec.ch2 +synctex-xe-rec.ch3 +synctex-xetex.h +synctex.c +synctex.defines +synctex.h +synctex_main.c +tests + + +Version: +-------- +This is version 1, which refers to the synctex output file format. +The files are identified by a build number. +In order to help developers to automatically manage the version and build numbers +and download the parser only when necessary, the synctex_parser.version +is an ASCII text file just containing the current version and build numbers. + +History: +-------- +1.1: Thu Jul 17 09:28:13 UTC 2008 +- First official version available in TeXLive 2008 DVD. + Unfortunately, the backwards synchronization is not working properly mainly for ConTeXt users, see below. +1.2: Tue Sep 2 10:28:32 UTC 2008 +- Correction for ConTeXt support in the edit query. + The previous method was assuming that TeX boxes do not overlap, + which is reasonable for LaTeX but not for ConTeXt. + This assumption is no longer considered. +1.3: Fri Sep 5 09:39:57 UTC 2008 +- Local variable "read" renamed to "already_read" to avoid conflicts. +- "inline" compiler directive renamed to "SYNCTEX_INLINE" for code support and maintenance +- _synctex_error cannot be inlined due to variable arguments (thanks Christiaan Hofman) +- Correction in the display query, extra boundary nodes are used for a more precise forwards synchronization +1.4: Fri Sep 12 08:12:34 UTC 2008 +- For an unknown reason, the previous version was not the real 1.3 (as used in iTeXMac2 build 747). + As a consequence, a crash was observed. +- Some typos are fixed. +1.6: Mon Nov 3 20:20:02 UTC 2008 +- The bug that prevented synchronization with compressed files on windows has been fixed. +- New interface to allow system specific customization. +- Note that some APIs have changed. +1.8: Mer 8 jul 2009 11:32:38 UTC +Note that version 1.7 was delivered privately. +- bug fix: synctex was causing a memory leak in pdftex and xetex, thus some processing speed degradation +- bug fix: the synctex command line tool was broken when updating a .synctex file +- enhancement: better accuracy of the synchronization process +- enhancement: the pdf output file and the associated .synctex file no longer need to live in the same directory. + The new -d option of the synctex command line tool manages this situation. + This is handy when using something like tex -output-directory=DIR ... +1.9: Wed Nov 4 11:52:35 UTC 2009 +- Various typo fixed +- OutputDebugString replaced by OutputDebugStringA to deliberately disable unicode preprocessing +- New conditional created because OutputDebugStringA is only available since Windows 2K professional +1.10: Sun Jan 10 10:12:32 UTC 2010 +- Bug fix in synctex_parser.c to solve a synchronization problem with amsmath's gather environment. + Concerns the synctex tool. +1.11: Sun Jan 17 09:12:31 UTC 2010 +- Bug fix in synctex_parser.c, function synctex_node_box_visible_v: 'x' replaced by 'y'. + Only 3rd party tools are concerned. +1.12: Mon Jul 19 21:52:10 UTC 2010 +- Bug fix in synctex_parser.c, function __synctex_open: the io_mode was modified even in case of a non zero return, +causing a void .synctex.gz file to be created even if it was not expected. Reported by Marek Kasik concerning a bug on evince. +1.13: Fri Mar 11 07:39:12 UTC 2011 +- Bug fix in synctex_parser.c, better synchronization as suggested by Jan Sundermeyer (near line 3388). +- Stronger code design in synctex_parser_utils.c, function _synctex_get_name (really neutral behavior). + Only 3rd party tools are concerned. +1.14: Fri Apr 15 19:10:57 UTC 2011 +- taking output_directory into account +- Replaced FOPEN_WBIN_MODE by FOPEN_W_MODE when opening the text version of the .synctex file. +- Merging with LuaTeX's version of synctex.c +1.15: Fri Jun 10 14:10:17 UTC 2011 +This concerns the synctex command line tool and 3rd party developers. +TeX and friends are not concerned by these changes. +- Bug fixed in _synctex_get_io_mode_name, sometimes the wrong mode was returned +- Support for LuaTeX convention of './' file prefixing +1.16: Tue Jun 14 08:23:30 UTC 2011 +This concerns the synctex command line tool and 3rd party developers. +TeX and friends are not concerned by these changes. +- Better forward search (thanks Jose Alliste) +- Support for LuaTeX convention of './' file prefixing now for everyone, not only for Windows +1.17: Fri Oct 14 08:15:16 UTC 2011 +This concerns the synctex command line tool and 3rd party developers. +TeX and friends are not concerned by these changes. +- synctex_parser.c: cosmetic changes to enhance code readability +- Better forward synchronization. + The problem occurs for example with LaTeX \item command. + The fact is that this command creates nodes at parse time but these nodes are used only + after the text material of the \item is displayed on the page. The consequence is that sometimes, + forward synchronization spots an irrelevant point from the point of view of the editing process. + This was due to some very basic filtering policy, where a somehow arbitrary choice was made when + many different possibilities where offered for synchronisation. + Now, forward synchronization prefers nodes inside an hbox with as many acceptable spots as possible. + This is achieved with the notion of mean line and node weight. +- Adding support for the new file naming convention with './' + + function synctex_ignore_leading_dot_slash_in_path replaces synctex_ignore_leading_dot_slash + + function _synctex_is_equivalent_file_name is more permissive + Previously, the function synctex_scanner_get_tag would give an answer only when + the given file name was EXACTLY one of the file names listed in the synctex file. + The we added some changes accepting for example 'foo.tex' instead of './foo.tex'. + Now we have an even looser policy for dealing with file names. + If the given file name does not match exactly one the file names of the synctex file, + then we try to match the base names. If there is only one match of the base names, + then it is taken as a match for the whole names. + The base name is defined as following: + ./foo => foo + /my///.////foo => foo + /foo => /foo + /my//.foo => /my//.foo +1.17: Tue Mar 13 10:10:03 UTC 2012 +- minor changes, no version changes +- syntax man pages are fixed as suggested by M. Shimata + see mail to tex-live@tug.org titled "syntax.5 has many warnings from groff" and "syntax.1 use invalid macro for mdoc" +1.17: Tue Jan 14 09:55:00 UTC 2014 +- fixed a segfault, from Sebastian Ramacher +1.17: Mon Aug 04 +- fixed a memory leak +1.18: Thu Jun 25 11:36:05 UTC 2015 +- nested sheets now fully supported (does it make sense in TeX) +- cosmetic changes: uniform indentation +- suppression of warnings, mainly long/int ones. In short, zlib likes ints when size_t likes longs. +- CLI synctex tool can build out of TeXLive (modulo appropriate options passed to the compiler) +1.19: Thu Mar 9 21:26:27 UTC 2017 +- the nested sheets patch was not a good solution. + It has been moved from the parser to the engine. + See the synctex.c source file for detailed explanations. +- there is a new synctex format specification. + We can see that a .synctex file can contain many times + the same vertical position because many objects belong + to the same line. When the options read -synctex=±2 or more, + a very basic compression algorithm is used: + if synctex is about write the same number then it writes + an = sign instead. This saves approximately 10% of the + synctex output file, either compressed or not. + The new synctex parser has been updated accordingly. + Actual tex frontend won't see any difference with the + TeX engines that include this new feature. + Frontends with the new parser won't see any difference + with the older TeX engines. + Frontends with the new parser will only see a difference + with new TeX engines if -synctex=±2 or more is used. + +Acknowledgments: +---------------- +The author received useful remarks from the pdfTeX developers, especially Hahn The Thanh, +and significant help from XeTeX developer Jonathan Kew + +Nota Bene: +---------- +If you include or use a significant part of the synctex package into a software, +I would appreciate to be listed as contributor and see "SyncTeX" highlighted. + +Copyright (c) 2008-2014 jerome DOT laurens AT u-bourgogne DOT fr + diff --git a/elpa/pdf-tools-20211110.513/build/server/synctex_parser_utils.c b/elpa/pdf-tools-20211110.513/build/server/synctex_parser_utils.c @@ -0,0 +1,570 @@ +/* + Copyright (c) 2008-2017 jerome DOT laurens AT u-bourgogne DOT fr + + This file is part of the __SyncTeX__ package. + + [//]: # (Latest Revision: Fri Jul 14 16:20:41 UTC 2017) + [//]: # (Version: 1.21) + + See `synctex_parser_readme.md` for more details + + ## License + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE + + Except as contained in this notice, the name of the copyright holder + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization from the copyright holder. + +*/ + +/* In this file, we find all the functions that may depend on the operating system. */ + +#include <synctex_parser_utils.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <stdio.h> + +#include <limits.h> +#include <ctype.h> +#include <string.h> + +#include <sys/stat.h> + +#if defined(_WIN32) || defined(__WIN32__) || defined(__TOS_WIN__) || defined(__WINDOWS__) +#define SYNCTEX_WINDOWS 1 +#endif + +#if defined(__OS2__) +#define SYNCTEX_OS2 1 +#endif + +#if defined(_WIN32) +#define SYNCTEX_RECENT_WINDOWS 1 +#endif + +#ifdef SYNCTEX_WINDOWS +#include <windows.h> +#include <shlwapi.h> /* Use shlwapi.lib */ +#endif + +void *_synctex_malloc(size_t size) { + void * ptr = malloc(size); + if(ptr) { + memset(ptr,0, size);/* ensures null termination of strings */ + } + return (void *)ptr; +} + +void _synctex_free(void * ptr) { + if (ptr) { + free(ptr); + } +} + +#if !defined(_WIN32) +# include <syslog.h> +#endif + +int _synctex_log(int level, const char * prompt, const char * reason,va_list arg) { + int result; +# ifdef SYNCTEX_RECENT_WINDOWS + {/* This code is contributed by William Blum. + As it does not work on some older computers, + the _WIN32 conditional here is replaced with a SYNCTEX_RECENT_WINDOWS one. + According to http://msdn.microsoft.com/en-us/library/aa363362(VS.85).aspx + Minimum supported client Windows 2000 Professional + Minimum supported server Windows 2000 Server + People running Windows 2K standard edition will not have OutputDebugStringA. + JL.*/ + char *buff; + size_t len; + OutputDebugStringA(prompt); + len = _vscprintf(reason, arg) + 1; + buff = (char*)malloc( len * sizeof(char) ); + result = vsprintf(buff, reason, arg) +strlen(prompt); + OutputDebugStringA(buff); + OutputDebugStringA("\n"); + free(buff); + } +# elif SYNCTEX_USE_SYSLOG + char * buffer1 = NULL; + char * buffer2 = NULL; + openlog ("SyncTeX", LOG_CONS | LOG_PID | LOG_PERROR | LOG_NDELAY, LOG_LOCAL0); + if (vasprintf(&buffer1,reason,arg)>=0 + && asprintf(&buffer2,"%s%s",prompt, buffer1)>=0) { + syslog (level, "%s", buffer2); + result = (int)strlen(buffer2); + } else { + syslog (level, "%s",prompt); + vsyslog(level,reason,arg); + result = (int)strlen(prompt); + } + free(buffer1); + free(buffer2); + closelog(); +# else + FILE * where = level == LOG_ERR? stderr: stdout; + result = fputs(prompt,where); + result += vfprintf(where, reason, arg); + result += fprintf(where,"\n"); +# endif + return result; +} + +int _synctex_error(const char * reason,...) { + va_list arg; + int result; + va_start (arg, reason); +#if defined(SYNCTEX_RECENT_WINDOWS) /* LOG_ERR is not used */ + result = _synctex_log(0, "! SyncTeX Error : ", reason, arg); +#else + result = _synctex_log(LOG_ERR, "! SyncTeX Error : ", reason, arg); +#endif + va_end (arg); + return result; +} + +int _synctex_debug(const char * reason,...) { + va_list arg; + int result; + va_start (arg, reason); +#if defined(SYNCTEX_RECENT_WINDOWS) /* LOG_DEBUG is not used */ + result = _synctex_log(0, "! SyncTeX Error : ", reason, arg); +#else + result = _synctex_log(LOG_DEBUG, "! SyncTeX Error : ", reason, arg); +#endif + va_end (arg); + return result; +} + +/* strip the last extension of the given string, this string is modified! */ +void _synctex_strip_last_path_extension(char * string) { + if(NULL != string){ + char * last_component = NULL; + char * last_extension = NULL; +# if defined(SYNCTEX_WINDOWS) + last_component = PathFindFileName(string); + last_extension = PathFindExtension(string); + if(last_extension == NULL)return; + if(last_component == NULL)last_component = string; + if(last_extension>last_component){/* filter out paths like "my/dir/.hidden" */ + last_extension[0] = '\0'; + } +# else + char * next = NULL; + /* first we find the last path component */ + if(NULL == (last_component = strstr(string,"/"))){ + last_component = string; + } else { + ++last_component; + while((next = strstr(last_component,"/"))){ + last_component = next+1; + } + } +# if defined(SYNCTEX_OS2) + /* On OS2, the '\' is also a path separator. */ + while((next = strstr(last_component,"\\"))){ + last_component = next+1; + } +# endif /* SYNCTEX_OS2 */ + /* then we find the last path extension */ + if((last_extension = strstr(last_component,"."))){ + ++last_extension; + while((next = strstr(last_extension,"."))){ + last_extension = next+1; + } + --last_extension;/* back to the "." */ + if(last_extension>last_component){/* filter out paths like ....my/dir/.hidden"*/ + last_extension[0] = '\0'; + } + } +# endif /* SYNCTEX_WINDOWS */ + } +} + +synctex_bool_t synctex_ignore_leading_dot_slash_in_path(const char ** name_ref) +{ + if (SYNCTEX_IS_DOT((*name_ref)[0]) && SYNCTEX_IS_PATH_SEPARATOR((*name_ref)[1])) { + do { + (*name_ref) += 2; + while (SYNCTEX_IS_PATH_SEPARATOR((*name_ref)[0])) { + ++(*name_ref); + } + } while(SYNCTEX_IS_DOT((*name_ref)[0]) && SYNCTEX_IS_PATH_SEPARATOR((*name_ref)[1])); + return synctex_YES; + } + return synctex_NO; +} + +/* The base name is necessary to deal with the 2011 file naming convention... + * path is a '\0' terminated string + * The return value is the trailing part of the argument, + * just following the first occurrence of the regexp pattern "[^|/|\].[\|/]+".*/ +const char * _synctex_base_name(const char *path) { + const char * ptr = path; + do { + if (synctex_ignore_leading_dot_slash_in_path(&ptr)) { + return ptr; + } + do { + if (!*(++ptr)) { + return path; + } + } while (!SYNCTEX_IS_PATH_SEPARATOR(*ptr)); + } while (*(++ptr)); + return path; +} + +/* Compare two file names, windows is sometimes case insensitive... */ +synctex_bool_t _synctex_is_equivalent_file_name(const char *lhs, const char *rhs) { + /* Remove the leading regex '(\./+)*' in both rhs and lhs */ + synctex_ignore_leading_dot_slash_in_path(&lhs); + synctex_ignore_leading_dot_slash_in_path(&rhs); +next_character: + if (SYNCTEX_IS_PATH_SEPARATOR(*lhs)) {/* lhs points to a path separator */ + if (!SYNCTEX_IS_PATH_SEPARATOR(*rhs)) {/* but not rhs */ + return synctex_NO; + } + ++lhs; + ++rhs; + synctex_ignore_leading_dot_slash_in_path(&lhs); + synctex_ignore_leading_dot_slash_in_path(&rhs); + goto next_character; + } else if (SYNCTEX_IS_PATH_SEPARATOR(*rhs)) {/* rhs points to a path separator but not lhs */ + return synctex_NO; + } else if (SYNCTEX_ARE_PATH_CHARACTERS_EQUAL(*lhs,*rhs)){/* uppercase do not match */ + return synctex_NO; + } else if (!*lhs) {/* lhs is at the end of the string */ + return *rhs ? synctex_NO : synctex_YES; + } else if(!*rhs) {/* rhs is at the end of the string but not lhs */ + return synctex_NO; + } + ++lhs; + ++rhs; + goto next_character; +} + +synctex_bool_t _synctex_path_is_absolute(const char * name) { + if(!strlen(name)) { + return synctex_NO; + } +# if defined(SYNCTEX_WINDOWS) || defined(SYNCTEX_OS2) + if(strlen(name)>2) { + return (name[1]==':' && SYNCTEX_IS_PATH_SEPARATOR(name[2]))?synctex_YES:synctex_NO; + } + return synctex_NO; +# else + return SYNCTEX_IS_PATH_SEPARATOR(name[0])?synctex_YES:synctex_NO; +# endif +} + +/* We do not take care of UTF-8 */ +const char * _synctex_last_path_component(const char * name) { + const char * c = name+strlen(name); + if(c>name) { + if(!SYNCTEX_IS_PATH_SEPARATOR(*c)) { + do { + --c; + if(SYNCTEX_IS_PATH_SEPARATOR(*c)) { + return c+1; + } + } while(c>name); + } + return c;/* the last path component is the void string*/ + } + return c; +} + +int _synctex_copy_with_quoting_last_path_component(const char * src, char ** dest_ref, size_t size) { + if(src && dest_ref) { + const char * lpc; +# define dest (*dest_ref) + dest = NULL; /* Default behavior: no change and success. */ + lpc = _synctex_last_path_component(src); + if(strlen(lpc)) { + if(strchr(lpc,' ') && lpc[0]!='"' && lpc[strlen(lpc)-1]!='"') { + /* We are in the situation where adding the quotes is allowed. */ + /* Time to add the quotes. */ + /* Consistency test: we must have dest+size>dest+strlen(dest)+2 + * or equivalently: strlen(dest)+2<size (see below) */ + if(strlen(src)<size) { + if((dest = (char *)malloc(size+2))) { + char * dpc = dest + (lpc-src); /* dpc is the last path component of dest. */ + if(dest != strncpy(dest,src,size)) { + _synctex_error("! _synctex_copy_with_quoting_last_path_component: Copy problem"); + free(dest); + dest = NULL;/* Don't forget to reinitialize. */ + return -2; + } + memmove(dpc+1,dpc,strlen(dpc)+1); /* Also move the null terminating character. */ + dpc[0]='"'; + dpc[strlen(dpc)+1]='\0';/* Consistency test */ + dpc[strlen(dpc)]='"'; + return 0; /* Success. */ + } + return -1; /* Memory allocation error. */ + } + _synctex_error("! _synctex_copy_with_quoting_last_path_component: Internal inconsistency"); + return -3; + } + return 0; /* Success. */ + } + return 0; /* No last path component. */ +# undef dest + } + return 1; /* Bad parameter, this value is subject to changes. */ +} + +/* The client is responsible of the management of the returned string, if any. */ +char * _synctex_merge_strings(const char * first,...); + +char * _synctex_merge_strings(const char * first,...) { + va_list arg; + size_t size = 0; + const char * temp; + /* First retrieve the size necessary to store the merged string */ + va_start (arg, first); + temp = first; + do { + size_t len = strlen(temp); + if(UINT_MAX-len<size) { + _synctex_error("! _synctex_merge_strings: Capacity exceeded."); + return NULL; + } + size+=len; + } while( (temp = va_arg(arg, const char *)) != NULL); + va_end(arg); + if(size>0) { + char * result = NULL; + ++size; + /* Create the memory storage */ + if(NULL!=(result = (char *)malloc(size))) { + char * dest = result; + va_start (arg, first); + temp = first; + do { + if((size = strlen(temp))>0) { + /* There is something to merge */ + if(dest != strncpy(dest,temp,size)) { + _synctex_error("! _synctex_merge_strings: Copy problem"); + free(result); + result = NULL; + return NULL; + } + dest += size; + } + } while( (temp = va_arg(arg, const char *)) != NULL); + va_end(arg); + dest[0]='\0';/* Terminate the merged string */ + return result; + } + _synctex_error("! _synctex_merge_strings: Memory problem"); + return NULL; + } + return NULL; +} + +/* The purpose of _synctex_get_name is to find the name of the synctex file. + * There is a list of possible filenames from which we return the most recent one and try to remove all the others. + * With two runs of pdftex or xetex we are sure the the synctex file is really the most appropriate. + */ +int _synctex_get_name(const char * output, const char * build_directory, char ** synctex_name_ref, synctex_io_mode_t * io_mode_ref) +{ + if(output && synctex_name_ref && io_mode_ref) { + /* If output is already absolute, we just have to manage the quotes and the compress mode */ + size_t size = 0; + char * synctex_name = NULL; + synctex_io_mode_t io_mode = *io_mode_ref; + const char * base_name = _synctex_last_path_component(output); /* do not free, output is the owner. base name of output*/ + /* Do we have a real base name ? */ + if(strlen(base_name)>0) { + /* Yes, we do. */ + const char * temp = NULL; + char * core_name = NULL; /* base name of output without path extension. */ + char * dir_name = NULL; /* dir name of output */ + char * quoted_core_name = NULL; + char * basic_name = NULL; + char * gz_name = NULL; + char * quoted_name = NULL; + char * quoted_gz_name = NULL; + char * build_name = NULL; + char * build_gz_name = NULL; + char * build_quoted_name = NULL; + char * build_quoted_gz_name = NULL; + struct stat buf; + time_t the_time = 0; + /* Create core_name: let temp point to the dot before the path extension of base_name; + * We start form the \0 terminating character and scan the string upward until we find a dot. + * The leading dot is not accepted. */ + if((temp = strrchr(base_name,'.')) && (size = temp - base_name)>0) { + /* There is a dot and it is not at the leading position */ + if(NULL == (core_name = (char *)malloc(size+1))) { + _synctex_error("! _synctex_get_name: Memory problem 1"); + return -1; + } + if(core_name != strncpy(core_name,base_name,size)) { + _synctex_error("! _synctex_get_name: Copy problem 1"); + free(core_name); + dir_name = NULL; + return -2; + } + core_name[size] = '\0'; + } else { + /* There is no path extension, + * Just make a copy of base_name */ + core_name = _synctex_merge_strings(base_name); + } + /* core_name is properly set up, owned by "self". */ + /* creating dir_name. */ + size = strlen(output)-strlen(base_name); + if(size>0) { + /* output contains more than one path component */ + if(NULL == (dir_name = (char *)malloc(size+1))) { + _synctex_error("! _synctex_get_name: Memory problem"); + free(core_name); + return -1; + } + if(dir_name != strncpy(dir_name,output,size)) { + _synctex_error("! _synctex_get_name: Copy problem"); + free(dir_name); + dir_name = NULL; + free(core_name); + dir_name = NULL; + return -2; + } + dir_name[size] = '\0'; + } + /* dir_name is properly set up. It ends with a path separator, if non void. */ + /* creating quoted_core_name. */ + if(strchr(core_name,' ')) { + quoted_core_name = _synctex_merge_strings("\"",core_name,"\""); + } + /* quoted_core_name is properly set up. */ + if(dir_name &&strlen(dir_name)>0) { + basic_name = _synctex_merge_strings(dir_name,core_name,synctex_suffix,NULL); + if(quoted_core_name && strlen(quoted_core_name)>0) { + quoted_name = _synctex_merge_strings(dir_name,quoted_core_name,synctex_suffix,NULL); + } + } else { + basic_name = _synctex_merge_strings(core_name,synctex_suffix,NULL); + if(quoted_core_name && strlen(quoted_core_name)>0) { + quoted_name = _synctex_merge_strings(quoted_core_name,synctex_suffix,NULL); + } + } + if(!_synctex_path_is_absolute(output) && build_directory && (size = strlen(build_directory))) { + temp = build_directory + size - 1; + if(_synctex_path_is_absolute(temp)) { + build_name = _synctex_merge_strings(build_directory,basic_name,NULL); + if(quoted_core_name && strlen(quoted_core_name)>0) { + build_quoted_name = _synctex_merge_strings(build_directory,quoted_name,NULL); + } + } else { + build_name = _synctex_merge_strings(build_directory,"/",basic_name,NULL); + if(quoted_core_name && strlen(quoted_core_name)>0) { + build_quoted_name = _synctex_merge_strings(build_directory,"/",quoted_name,NULL); + } + } + } + if(basic_name) { + gz_name = _synctex_merge_strings(basic_name,synctex_suffix_gz,NULL); + } + if(quoted_name) { + quoted_gz_name = _synctex_merge_strings(quoted_name,synctex_suffix_gz,NULL); + } + if(build_name) { + build_gz_name = _synctex_merge_strings(build_name,synctex_suffix_gz,NULL); + } + if(build_quoted_name) { + build_quoted_gz_name = _synctex_merge_strings(build_quoted_name,synctex_suffix_gz,NULL); + } + /* All the others names are properly set up... */ + /* retain the most recently modified file */ +# define TEST(FILENAME,COMPRESS_MODE) \ + if(FILENAME) {\ + if (stat(FILENAME, &buf)) { \ + free(FILENAME);\ + FILENAME = NULL;\ + } else if (buf.st_mtime>the_time) { \ + the_time=buf.st_mtime; \ + synctex_name = FILENAME; \ + if (COMPRESS_MODE) { \ + io_mode |= synctex_io_gz_mask; \ + } else { \ + io_mode &= ~synctex_io_gz_mask; \ + } \ + } \ + } + TEST(basic_name,synctex_DONT_COMPRESS); + TEST(gz_name,synctex_COMPRESS); + TEST(quoted_name,synctex_DONT_COMPRESS); + TEST(quoted_gz_name,synctex_COMPRESS); + TEST(build_name,synctex_DONT_COMPRESS); + TEST(build_gz_name,synctex_COMPRESS); + TEST(build_quoted_name,synctex_DONT_COMPRESS); + TEST(build_quoted_gz_name,synctex_COMPRESS); +# undef TEST + /* Free all the intermediate filenames, except the one that will be used as returned value. */ +# define CLEAN_AND_REMOVE(FILENAME) \ + if(FILENAME && (FILENAME!=synctex_name)) {\ + remove(FILENAME);\ + printf("synctex tool info: %s removed\n",FILENAME);\ + free(FILENAME);\ + FILENAME = NULL;\ + } + CLEAN_AND_REMOVE(basic_name); + CLEAN_AND_REMOVE(gz_name); + CLEAN_AND_REMOVE(quoted_name); + CLEAN_AND_REMOVE(quoted_gz_name); + CLEAN_AND_REMOVE(build_name); + CLEAN_AND_REMOVE(build_gz_name); + CLEAN_AND_REMOVE(build_quoted_name); + CLEAN_AND_REMOVE(build_quoted_gz_name); +# undef CLEAN_AND_REMOVE + /* set up the returned values */ + * synctex_name_ref = synctex_name; + /* synctex_name won't always end in .gz, even when compressed. */ + FILE * F = fopen(synctex_name, "r"); + if (F != NULL) { + if (!feof(F) + && 31 == fgetc(F) + && !feof(F) + && 139 == fgetc(F)) { + io_mode = synctex_compress_mode_gz; + } + fclose(F); + } + * io_mode_ref = io_mode; + return 0; + } + return -1;/* bad argument */ + } + return -2; +} + +const char * _synctex_get_io_mode_name(synctex_io_mode_t io_mode) { + static const char * synctex_io_modes[4] = {"r","rb","a","ab"}; + unsigned index = ((io_mode & synctex_io_gz_mask)?1:0) + ((io_mode & synctex_io_append_mask)?2:0);// bug pointed out by Jose Alliste + return synctex_io_modes[index]; +} diff --git a/elpa/pdf-tools-20211110.513/build/server/synctex_parser_utils.h b/elpa/pdf-tools-20211110.513/build/server/synctex_parser_utils.h @@ -0,0 +1,163 @@ +/* + Copyright (c) 2008-2017 jerome DOT laurens AT u-bourgogne DOT fr + + This file is part of the __SyncTeX__ package. + + [//]: # (Latest Revision: Fri Jul 14 16:20:41 UTC 2017) + [//]: # (Version: 1.21) + + See `synctex_parser_readme.md` for more details + + ## License + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE + +Except as contained in this notice, the name of the copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in this Software without prior written +authorization from the copyright holder. + +*/ + +#ifndef SYNCTEX_PARSER_UTILS_H +#define SYNCTEX_PARSER_UTILS_H + +/* The utilities declared here are subject to conditional implementation. + * All the operating system special stuff goes here. + * The problem mainly comes from file name management: path separator, encoding... + */ + +#include "synctex_version.h" + +typedef int synctex_bool_t; +# define synctex_YES (0==0) +# define synctex_NO (0==1) + +# define synctex_ADD_QUOTES -1 +# define synctex_COMPRESS -1 +# define synctex_DONT_ADD_QUOTES 0 +# define synctex_DONT_COMPRESS 0 + +#ifndef __SYNCTEX_PARSER_UTILS__ +# define __SYNCTEX_PARSER_UTILS__ + +#include <stdlib.h> + +#ifdef __cplusplus +extern "C" { +#endif + +# if defined(_WIN32) || defined(__OS2__) +# define SYNCTEX_CASE_SENSITIVE_PATH 0 +# define SYNCTEX_IS_PATH_SEPARATOR(c) ('/' == c || '\\' == c) +# else +# define SYNCTEX_CASE_SENSITIVE_PATH 1 +# define SYNCTEX_IS_PATH_SEPARATOR(c) ('/' == c) +# endif + +# if defined(_WIN32) || defined(__OS2__) +# define SYNCTEX_IS_DOT(c) ('.' == c) +# else +# define SYNCTEX_IS_DOT(c) ('.' == c) +# endif + +# if SYNCTEX_CASE_SENSITIVE_PATH +# define SYNCTEX_ARE_PATH_CHARACTERS_EQUAL(left,right) (left != right) +# else +# define SYNCTEX_ARE_PATH_CHARACTERS_EQUAL(left,right) (toupper(left) != toupper(right)) +# endif + +/* This custom malloc functions initializes to 0 the newly allocated memory. + * There is no bzero function on windows. */ +void *_synctex_malloc(size_t size); + +/* To balance _synctex_malloc. + * ptr might be NULL. */ +void _synctex_free(void * ptr); + +/* This is used to log some informational message to the standard error stream. + * On Windows, the stderr stream is not exposed and another method is used. + * The return value is the number of characters printed. */ + int _synctex_error(const char * reason,...); + int _synctex_debug(const char * reason,...); + +/* strip the last extension of the given string, this string is modified! + * This function depends on the OS because the path separator may differ. + * This should be discussed more precisely. */ +void _synctex_strip_last_path_extension(char * string); + +/* Compare two file names, windows is sometimes case insensitive... + * The given strings may differ stricto sensu, but represent the same file name. + * It might not be the real way of doing things. + * The return value is an undefined non 0 value when the two file names are equivalent. + * It is 0 otherwise. */ +synctex_bool_t _synctex_is_equivalent_file_name(const char *lhs, const char *rhs); + +/* Description forthcoming.*/ +synctex_bool_t _synctex_path_is_absolute(const char * name); + +/* Description forthcoming...*/ +const char * _synctex_last_path_component(const char * name); + +/* Description forthcoming...*/ +const char * _synctex_base_name(const char *path); + +/* If the core of the last path component of src is not already enclosed with double quotes ('"') + * and contains a space character (' '), then a new buffer is created, the src is copied and quotes are added. + * In all other cases, no destination buffer is created and the src is not copied. + * 0 on success, which means no error, something non 0 means error, mainly due to memory allocation failure, or bad parameter. + * This is used to fix a bug in the first version of pdftex with synctex (1.40.9) for which names with spaces + * were not managed in a standard way. + * On success, the caller owns the buffer pointed to by dest_ref (is any) and + * is responsible of freeing the memory when done. + * The size argument is the size of the src buffer. On return the dest_ref points to a buffer sized size+2.*/ +int _synctex_copy_with_quoting_last_path_component(const char * src, char ** dest_ref, size_t size); + +/* These are the possible extensions of the synctex file */ +extern const char * synctex_suffix; +extern const char * synctex_suffix_gz; + +typedef unsigned int synctex_io_mode_t; + +typedef enum { + synctex_io_append_mask = 1, + synctex_io_gz_mask = synctex_io_append_mask<<1 +} synctex_io_mode_masks_t; + +typedef enum { + synctex_compress_mode_none = 0, + synctex_compress_mode_gz = 1 +} synctex_compress_mode_t; + +int _synctex_get_name(const char * output, const char * build_directory, char ** synctex_name_ref, synctex_io_mode_t * io_mode_ref); + +/* returns the correct mode required by fopen and gzopen from the given io_mode */ +const char * _synctex_get_io_mode_name(synctex_io_mode_t io_mode); + +synctex_bool_t synctex_ignore_leading_dot_slash_in_path(const char ** name); + +#ifdef __cplusplus +} +#endif + +#endif +#endif /* SYNCTEX_PARSER_UTILS_H */ diff --git a/elpa/pdf-tools-20211110.513/build/server/synctex_parser_version.txt b/elpa/pdf-tools-20211110.513/build/server/synctex_parser_version.txt @@ -0,0 +1 @@ +1.21 diff --git a/elpa/pdf-tools-20211110.513/build/server/synctex_version.h b/elpa/pdf-tools-20211110.513/build/server/synctex_version.h @@ -0,0 +1,59 @@ +/* +Copyright (c) 2008-2017 jerome DOT laurens AT u-bourgogne DOT fr + +This file is part of the __SyncTeX__ package. + +[//]: # (Latest Revision: Fri Jul 14 16:20:41 UTC 2017) +[//]: # (Version: 1.21) + +See `synctex_parser_readme.md` for more details + +## License + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE + +Except as contained in this notice, the name of the copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in this Software without prior written +authorization from the copyright holder. + +## Acknowledgments: + +The author received useful remarks from the __pdfTeX__ developers, especially Hahn The Thanh, +and significant help from __XeTeX__ developer Jonathan Kew. + +## Nota Bene: + +If you include or use a significant part of the __SyncTeX__ package into a software, +I would appreciate to be listed as contributor and see "__SyncTeX__" highlighted. +*/ + +#ifndef __SYNCTEX_VERSION__ +# define __SYNCTEX_VERSION__ + +# define SYNCTEX_VERSION_MAJOR 1 + +# define SYNCTEX_VERSION_STRING "1.21" + +# define SYNCTEX_CLI_VERSION_STRING "1.5" + +#endif diff --git a/elpa/pdf-tools-20211110.513/build/server/test/docker/.gitignore b/elpa/pdf-tools-20211110.513/build/server/test/docker/.gitignore @@ -0,0 +1,2 @@ +*.Dockerfile +*.build diff --git a/elpa/pdf-tools-20211110.513/build/server/test/docker/lib/run-tests b/elpa/pdf-tools-20211110.513/build/server/test/docker/lib/run-tests @@ -0,0 +1,9 @@ +#!/bin/sh + +PATH="$(dirname "$0")":$PATH + +set -e + +yes-or-enter | ./autobuild -i /bin +yes-or-enter | ./autobuild -i /usr/bin | \ + grep -q "Skipping package installation (already installed)" diff --git a/elpa/pdf-tools-20211110.513/build/server/test/docker/lib/yes-or-enter b/elpa/pdf-tools-20211110.513/build/server/test/docker/lib/yes-or-enter @@ -0,0 +1,9 @@ +#!/bin/bash + +# Step over prompts from the package-manager. +if [ -f /etc/arch-release ]; then + yes '' +else + yes +fi + diff --git a/elpa/pdf-tools-20211110.513/build/server/test/docker/templates/Dockerfile.in b/elpa/pdf-tools-20211110.513/build/server/test/docker/templates/Dockerfile.in @@ -0,0 +1,4 @@ +ADD . /epdfinfo +WORKDIR /epdfinfo +RUN make -s distclean || true +CMD ["sh", "./test/docker/lib/run-tests"] diff --git a/elpa/pdf-tools-20211110.513/build/server/test/docker/templates/arch.Dockerfile.in b/elpa/pdf-tools-20211110.513/build/server/test/docker/templates/arch.Dockerfile.in @@ -0,0 +1,4 @@ +# -*- dockerfile -*- +FROM archlinux:latest +RUN pacman -Syu --noconfirm --noprogressbar && \ + pacman -S --noconfirm --noprogressbar poppler-glib base-devel diff --git a/elpa/pdf-tools-20211110.513/build/server/test/docker/templates/centos-7.Dockerfile.in b/elpa/pdf-tools-20211110.513/build/server/test/docker/templates/centos-7.Dockerfile.in @@ -0,0 +1,3 @@ +# -*- dockerfile -*- +FROM centos:7 +RUN yum update -y && yum install -y gcc gcc-c++ poppler-glib-devel diff --git a/elpa/pdf-tools-20211110.513/build/server/test/docker/templates/debian-10.Dockerfile.in b/elpa/pdf-tools-20211110.513/build/server/test/docker/templates/debian-10.Dockerfile.in @@ -0,0 +1,3 @@ +# -*- dockerfile -*- +FROM debian:10 +RUN apt-get update -y && apt-get install -y gcc g++ libpoppler-glib-dev diff --git a/elpa/pdf-tools-20211110.513/build/server/test/docker/templates/debian-8.Dockerfile.in b/elpa/pdf-tools-20211110.513/build/server/test/docker/templates/debian-8.Dockerfile.in @@ -0,0 +1,4 @@ +# -*- dockerfile -*- +FROM debian:8 +RUN apt-get update -y && apt-get install -y gcc g++ libpoppler-glib-dev + diff --git a/elpa/pdf-tools-20211110.513/build/server/test/docker/templates/debian-9.Dockerfile.in b/elpa/pdf-tools-20211110.513/build/server/test/docker/templates/debian-9.Dockerfile.in @@ -0,0 +1,4 @@ +# -*- dockerfile -*- +FROM debian:9 +RUN apt-get update -y && apt-get install -y gcc g++ libpoppler-glib-dev + diff --git a/elpa/pdf-tools-20211110.513/build/server/test/docker/templates/fedora-32.Dockerfile.in b/elpa/pdf-tools-20211110.513/build/server/test/docker/templates/fedora-32.Dockerfile.in @@ -0,0 +1,3 @@ +# -*- dockerfile -*- +FROM fedora:32 +RUN dnf update -y && dnf install -y gcc gcc-c++ poppler-glib-devel diff --git a/elpa/pdf-tools-20211110.513/build/server/test/docker/templates/fedora-33.Dockerfile.in b/elpa/pdf-tools-20211110.513/build/server/test/docker/templates/fedora-33.Dockerfile.in @@ -0,0 +1,3 @@ +# -*- dockerfile -*- +FROM fedora:33 +RUN dnf update -y && dnf install -y gcc gcc-c++ poppler-glib-devel diff --git a/elpa/pdf-tools-20211110.513/build/server/test/docker/templates/fedora-34.Dockerfile.in b/elpa/pdf-tools-20211110.513/build/server/test/docker/templates/fedora-34.Dockerfile.in @@ -0,0 +1,3 @@ +# -*- dockerfile -*- +FROM fedora:34 +RUN dnf update -y && dnf install -y gcc gcc-c++ poppler-glib-devel diff --git a/elpa/pdf-tools-20211110.513/build/server/test/docker/templates/fedora-35.Dockerfile.in b/elpa/pdf-tools-20211110.513/build/server/test/docker/templates/fedora-35.Dockerfile.in @@ -0,0 +1,3 @@ +# -*- dockerfile -*- +FROM fedora:35 +RUN dnf update -y && dnf install -y gcc gcc-c++ poppler-glib-devel diff --git a/elpa/pdf-tools-20211110.513/build/server/test/docker/templates/gentoo.Dockerfile.in b/elpa/pdf-tools-20211110.513/build/server/test/docker/templates/gentoo.Dockerfile.in @@ -0,0 +1,5 @@ +# -*- dockerfile -*- +FROM gentoo/stage3-amd64 +RUN emerge --sync && emerge sys-devel/gcc app-text/poppler + + diff --git a/elpa/pdf-tools-20211110.513/build/server/test/docker/templates/ubuntu-14.Dockerfile.in b/elpa/pdf-tools-20211110.513/build/server/test/docker/templates/ubuntu-14.Dockerfile.in @@ -0,0 +1,4 @@ +# -*- dockerfile -*- +FROM ubuntu:trusty +RUN apt-get update -y && apt-get install -y gcc g++ libpoppler-glib-dev + diff --git a/elpa/pdf-tools-20211110.513/build/server/test/docker/templates/ubuntu-16.Dockerfile.in b/elpa/pdf-tools-20211110.513/build/server/test/docker/templates/ubuntu-16.Dockerfile.in @@ -0,0 +1,4 @@ +# -*- dockerfile -*- +FROM ubuntu:xenial +RUN apt-get update -y && apt-get install -y gcc g++ libpoppler-glib-dev + diff --git a/elpa/pdf-tools-20211110.513/build/server/test/docker/templates/ubuntu-18.Dockerfile.in b/elpa/pdf-tools-20211110.513/build/server/test/docker/templates/ubuntu-18.Dockerfile.in @@ -0,0 +1,3 @@ +# -*- dockerfile -*- +FROM ubuntu:bionic +RUN apt-get update -y && apt-get install -y gcc g++ libpoppler-glib-dev diff --git a/elpa/pdf-tools-20211110.513/build/server/test/docker/templates/ubuntu-20.10.Dockerfile.in b/elpa/pdf-tools-20211110.513/build/server/test/docker/templates/ubuntu-20.10.Dockerfile.in @@ -0,0 +1,3 @@ +# -*- dockerfile -*- +FROM ubuntu:groovy +RUN apt-get update -y && apt-get install -y gcc g++ libpoppler-glib-dev diff --git a/elpa/pdf-tools-20211110.513/build/server/test/docker/templates/ubuntu-20.Dockerfile.in b/elpa/pdf-tools-20211110.513/build/server/test/docker/templates/ubuntu-20.Dockerfile.in @@ -0,0 +1,3 @@ +# -*- dockerfile -*- +FROM ubuntu:focal +RUN apt-get update -y && apt-get install -y gcc g++ libpoppler-glib-dev diff --git a/elpa/pdf-tools-20211110.513/build/server/test/docker/templates/ubuntu-21.Dockerfile.in b/elpa/pdf-tools-20211110.513/build/server/test/docker/templates/ubuntu-21.Dockerfile.in @@ -0,0 +1,3 @@ +# -*- dockerfile -*- +FROM ubuntu:hirsute +RUN apt-get update -y && apt-get install -y gcc g++ libpoppler-glib-dev diff --git a/elpa/pdf-tools-20211110.513/epdfinfo b/elpa/pdf-tools-20211110.513/epdfinfo Binary files differ. diff --git a/elpa/pdf-tools-20211110.513/pdf-annot.el b/elpa/pdf-tools-20211110.513/pdf-annot.el @@ -0,0 +1,1791 @@ +;;; pdf-annot.el --- Annotation support for PDF files. -*- lexical-binding: t -*- + +;; Copyright (C) 2013, 2014 Andreas Politz + +;; Author: Andreas Politz <politza@fh-trier.de> +;; Keywords: + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: +;; + + +(require 'pdf-view) +(require 'pdf-info) +(require 'pdf-cache) +(require 'pdf-misc) +(require 'facemenu) ;; list-colors-duplicates +(require 'faces) ;; color-values +(require 'org) ;; org-create-formula-image +(require 'tablist) +(require 'cl-lib) + + +;; * ================================================================== * +;; * Customizations +;; * ================================================================== * + +(defgroup pdf-annot nil + "Annotation support for PDF documents." + :group 'pdf-tools) + +(defcustom pdf-annot-activate-handler-functions nil + "A list of functions to activate a annotation. + +The functions on this hook will be called when some annotation is +activated, usually by a mouse-click. Each one is called with the +annotation as a single argument and it should return a non-nil +value if it has `handled' it. If no such function exists, the +default handler `pdf-annot-default-handler' will be +called. + +This hook is meant to allow for custom annotations. FIXME: +Implement and describe basic org example." + :group 'pdf-annot + :type 'hook) + +(defcustom pdf-annot-default-text-annotation-properties nil + "Alist of initial properties for new text annotations." + :group 'pdf-annot + :type '(alist :key-type symbol :value-type sexp)) + +(defcustom pdf-annot-default-markup-annotation-properties nil + "Alist of initial properties for new markup annotations." + :group 'pdf-annot + :type '(alist :key-type symbol :value-type sexp)) + +(make-obsolete-variable 'pdf-annot-default-text-annotation-properties + 'pdf-annot-default-annotation-properties + "0.90") + +(make-obsolete-variable 'pdf-annot-default-markup-annotation-properties + 'pdf-annot-default-annotation-properties + "0.90") + +(defcustom pdf-annot-default-annotation-properties + `((t (label . ,user-full-name)) + (text (icon . "Note") + (color . "#ff0000")) + (highlight (color . "yellow")) + (squiggly (color . "orange")) + (strike-out(color . "red")) + (underline (color . "blue"))) + "An alist of initial properties for new annotations. + +The alist contains a sub-alist for each of the currently available +annotation types, i.e. text, highlight, squiggly, strike-out and +underline. Additionally a sub-alist with a key of t acts as a default +entry. + +Each of these sub-alists contain default property-values of newly +added annotations of its respective type. + +Some of the most important properties and their types are label +\(a string\), contents \(a string\), color \(a color\) and, for +text-annotations only, icon \(one of the standard icon-types, see +`pdf-annot-standard-text-icons'\). + +For example a value of + + \(\(t \(color . \"red\"\) + \(label . \"Joe\"\) + \(highlight \(color . \"green\"\)\) + +would use a green color for highlight and a red one for other +annotations. Additionally the label for all annotations is set +to \"Joe\"." + + :group 'pdf-annot + :type (let* ((label '(cons :tag "Label" (const label) string)) + (contents '(cons :tag "Contents" (const contents) string)) + (color '(cons :tag "Color" (const color) color)) + (icon `(cons :tag "Icon" + (const icon) + (choice + ,@(mapcar (lambda (icon) + `(const ,icon)) + '("Note" "Comment" "Key" "Help" "NewParagraph" + "Paragraph" "Insert" "Cross" "Circle"))))) + (other '(repeat + :tag "Other properties" + (cons :tag "Property" + (symbol :tag "Key ") + (sexp :tag "Value")))) + (text-properties + `(set ,label ,contents ,color ,icon ,other)) + (markup-properties + `(set ,label ,contents ,color)) + (all-properties + `(set ,label ,contents ,color ,icon ,other))) + `(set + (cons :tag "All Annotations" (const t) ,all-properties) + (cons :tag "Text Annotations" (const text) ,text-properties) + (cons :tag "Highlight Annotations" (const highlight) ,markup-properties) + (cons :tag "Underline Annotations" (const underline) ,markup-properties) + (cons :tag "Squiggly Annotations" (const squiggly) ,markup-properties) + (cons :tag "Strike-out Annotations" (const strike-out) ,markup-properties)))) + +(defcustom pdf-annot-print-annotation-functions + '(pdf-annot-print-annotation-latex-maybe) + "A alist of functions for printing annotations, e.g. for the tooltip. + +The functions receive the annotation as single argument and +should return either a string or nil. The first string returned +will be used. + +If all of them return nil, the default function +`pdf-annot-print-annotation-default' is used." + :group 'pdf-annot + :type 'hook) + +(defcustom pdf-annot-latex-string-predicate + (lambda (str) + (and str (string-match "\\`[[:space:]\n]*[$\\]" str))) + "A predicate for recognizing LaTeX fragments. + +It receives a string and should return non-nil, if string is a +LaTeX fragment." + :group 'pdf-annot + :type 'function) + +(defcustom pdf-annot-latex-header + (concat org-format-latex-header + "\n\\setlength{\\textwidth}{12cm}") + "Header used when latex compiling annotations. +The default value is `org-format-latex-header' + +\"\\n\\\\setlength{\\\\textwidth}{12cm}\"." + :group 'pdf-annot + :type 'string) + +(defcustom pdf-annot-tweak-tooltips t + "Whether this package should tweak some settings regarding tooltips. + +If this variable has a non-nil value, + +`x-gtk-use-system-tooltips' is set to nil if appropriate, in +order to display text properties; + +`tooltip-hide-delay' is set to infinity, in order to not being +annoyed while reading the annotations." + :group 'pdf-annot + :type 'boolean) + +(defcustom pdf-annot-activate-created-annotations nil + "Whether to activate (i.e. edit) created annotations." + :group 'pdf-annot + :type 'boolean) + +(defcustom pdf-annot-attachment-display-buffer-action nil + "The display action used when displaying attachments." + :group 'pdf-annot + :type display-buffer--action-custom-type) + +(defconst pdf-annot-annotation-types + '(3d caret circle file + free-text highlight ink line link movie poly-line polygon popup + printer-mark screen sound square squiggly stamp strike-out text + trap-net underline unknown watermark widget) + "Complete list of annotation types.") + +(defcustom pdf-annot-list-listed-types + (if (pdf-info-markup-annotations-p) + (list 'text 'file 'squiggly 'highlight 'underline 'strike-out) + (list 'text 'file)) + "A list of annotation types displayed in the list buffer." + :group 'pdf-annot + :type `(set ,@(mapcar (lambda (type) + (list 'const type)) + pdf-annot-annotation-types))) + + +;; * ================================================================== * +;; * Variables and Macros +;; * ================================================================== * + +(defvar pdf-annot-color-history nil + "A list of recently used colors for annotations.") + +(defvar-local pdf-annot-modified-functions nil + "Functions to call, when an annotation was modified. + +A function on this hook should accept one argument: A CLOSURE +containing inserted, changed and deleted annotations. + +It may access these annotations by calling CLOSURE with one of +these arguments: + +`:inserted' The list of recently added annotations. + +`:deleted' The list of recently deleted annotations. + +`:changed' The list of recently changed annotations. + +`t' The union of recently added, deleted or changed annotations. + +`nil' Just returns nil. + +Any other argument signals an error.") + +(defconst pdf-annot-text-annotation-size '(24 . 24) + "The Size of text and file annotations in PDF points. + +These values are hard-coded in poppler. And while the size of +these annotations may be changed, i.e. the edges property, it has +no effect on the rendering.") + +(defconst pdf-annot-markup-annotation-types + '(text link free-text line square + circle polygon poly-line highlight underline squiggly + strike-out stamp caret ink file sound) + "List of defined markup annotation types.") + +(defconst pdf-annot-standard-text-icons + '("Note" "Comment" "Key" "Help" "NewParagraph" + "Paragraph" "Insert" "Cross" "Circle") + "A list of standard icon properties for text annotations.") + +(defvar pdf-annot-inhibit-modification-hooks nil + "Non-nil, if running `pdf-annot-modified-functions' should be + inhibited after some annotation has changed.") + +(defvar-local pdf-annot-delayed-modified-annotations nil + "A plist of not yet propagated modifications. + +It contains three entries :change, :delete and :insert. Each one +having a list of annotations as value.") + +(defvar-local pdf-annot--attachment-file-alist nil + "Alist mapping attachment ids to unique relative filenames.") + +(defmacro pdf-annot-with-atomic-modifications (&rest body) + "Execute BODY joining multiple modifications. + +The effect is, that `pdf-annot-modified-functions' will be called +only once at the end of BODY. + +BODY should not modify annotations in a different then the +current buffer, because that won't run the hooks properly." + (declare (indent 0) (debug t)) + `(unwind-protect + (save-current-buffer + (let ((pdf-annot-inhibit-modification-hooks t)) + (progn ,@body))) + (pdf-annot-run-modified-hooks))) + + +;; * ================================================================== * +;; * Minor mode +;; * ================================================================== * + +(defcustom pdf-annot-minor-mode-map-prefix (kbd "C-c C-a") + "The prefix to use for `pdf-annot-minor-mode-map'. + +Setting this after the package was loaded has no effect." + :group 'pdf-annot + :type 'key-sequence) + +(defvar pdf-annot-minor-mode-map + (let ((kmap (make-sparse-keymap)) + (smap (make-sparse-keymap))) + (define-key kmap pdf-annot-minor-mode-map-prefix smap) + (define-key smap "l" 'pdf-annot-list-annotations) + ;; (define-key smap "d" 'pdf-annot-toggle-display-annotations) + (define-key smap "a" 'pdf-annot-attachment-dired) + (when (pdf-info-writable-annotations-p) + (define-key smap "D" 'pdf-annot-delete) + (define-key smap "t" 'pdf-annot-add-text-annotation) + (when (pdf-info-markup-annotations-p) + (define-key smap "m" 'pdf-annot-add-markup-annotation) + (define-key smap "s" 'pdf-annot-add-squiggly-markup-annotation) + (define-key smap "u" 'pdf-annot-add-underline-markup-annotation) + (define-key smap "o" 'pdf-annot-add-strikeout-markup-annotation) + (define-key smap "h" 'pdf-annot-add-highlight-markup-annotation))) + kmap) + "Keymap used for `pdf-annot-minor-mode'.") + +(defvar savehist-minibuffer-history-variables) + +;;;###autoload +(define-minor-mode pdf-annot-minor-mode + "Support for PDF Annotations. + +\\{pdf-annot-minor-mode-map}" + :group 'pdf-annot + (cond + (pdf-annot-minor-mode + (when pdf-annot-tweak-tooltips + (when (boundp 'x-gtk-use-system-tooltips) + (setq x-gtk-use-system-tooltips nil)) + (setq tooltip-hide-delay 3600)) + (pdf-view-add-hotspot-function 'pdf-annot-hotspot-function 9) + (add-hook 'pdf-info-close-document-hook + 'pdf-annot-attachment-delete-base-directory nil t) + (when (featurep 'savehist) + (add-to-list 'savehist-minibuffer-history-variables + 'pdf-annot-color-history))) + (t + (pdf-view-remove-hotspot-function 'pdf-annot-hotspot-function) + (remove-hook 'pdf-info-close-document-hook + 'pdf-annot-attachment-delete-base-directory t))) + (pdf-view-redisplay t)) + +(defun pdf-annot-create-context-menu (a) + "Create a appropriate context menu for annotation A." + (let ((menu (make-sparse-keymap))) + ;; (when (and (bound-and-true-p pdf-misc-menu-bar-minor-mode) + ;; (bound-and-true-p pdf-misc-install-popup-menu)) + ;; (set-keymap-parent menu + ;; (lookup-key pdf-misc-menu-bar-minor-mode-map + ;; [menu-bar pdf-tools])) + ;; (define-key menu [sep-99] menu-bar-separator)) + (when (pdf-info-writable-annotations-p) + (define-key menu [delete-annotation] + `(menu-item "Delete annotation" + ,(lambda () + (interactive) + (pdf-annot-delete a) + (message "Annotation deleted")) + :help + "Delete this annotation."))) + (define-key menu [goto-annotation] + `(menu-item "List annotation" + ,(lambda () + (interactive) + (pdf-annot-show-annotation a t) + (pdf-annot-list-annotations) + (pdf-annot-list-goto-annotation a)) + :help "Find this annotation in the list buffer.")) + (when (pdf-annot-text-annotation-p a) + (define-key menu [change-text-icon] + `(menu-item "Change icon" + ,(pdf-annot-create-icon-submenu a) + :help "Change the appearance of this annotation."))) + (define-key menu [change-color] + `(menu-item "Change color" + ,(pdf-annot-create-color-submenu a) + :help "Change the appearance of this annotation.")) + (define-key menu [activate-annotation] + `(menu-item "Activate" + ,(lambda () + (interactive) + (pdf-annot-activate-annotation a)) + :help "Activate this annotation.")) + menu)) + +(defun pdf-annot-create-color-submenu (a) + (let ((menu (make-sparse-keymap))) + (define-key menu [color-chooser] + `(menu-item "Choose ..." + ,(lambda () + (interactive) + (list-colors-display + nil "*Choose annotation color*" + ;; list-colors-print does not like closures. + (let ((callback (make-symbol "xcallback"))) + (fset callback + (lambda (color) + (pdf-annot-put a 'color color) + (setq pdf-annot-color-history + (cons color + (remove color pdf-annot-color-history))) + (quit-window t))) + (list 'function callback)))))) + (dolist (color (butlast (reverse pdf-annot-color-history) + (max 0 (- (length pdf-annot-color-history) + 12)))) + (define-key menu (vector (intern (format "color-%s" color))) + `(menu-item ,color + ,(lambda nil + (interactive) + (pdf-annot-put a 'color color))))) + menu)) + +(defun pdf-annot-create-icon-submenu (a) + (let ((menu (make-sparse-keymap))) + (dolist (icon (reverse pdf-annot-standard-text-icons)) + (define-key menu (vector (intern (format "icon-%s" icon))) + `(menu-item ,icon + ,(lambda nil + (interactive) + (pdf-annot-put a 'icon icon))))) + menu)) + +;; * ================================================================== * +;; * Annotation Basics +;; * ================================================================== * + +(defun pdf-annot-create (alist &optional buffer) + "Create a annotation from ALIST in BUFFER. + +ALIST should be a property list as returned by +`pdf-cache-getannots'. BUFFER should be the buffer of the +corresponding PDF document. It defaults to the current buffer." + + (cons `(buffer . ,(or buffer (current-buffer))) + alist)) + +(defun pdf-annot-getannots (&optional pages types buffer) + "Return a list of annotations on PAGES of TYPES in BUFFER. + +See `pdf-info-normalize-pages' for valid values of PAGES. TYPES +may be a symbol or list of symbols denoting annotation types. + +PAGES defaults to all pages, TYPES to all types and BUFFER to the +current buffer." + + (pdf-util-assert-pdf-buffer buffer) + (unless buffer + (setq buffer (current-buffer))) + (unless (listp types) + (setq types (list types))) + (with-current-buffer buffer + (let (result) + (dolist (a (pdf-info-getannots pages)) + (when (or (null types) + (memq (pdf-annot-get a 'type) types)) + (push (pdf-annot-create a) result))) + result))) + +(defun pdf-annot-getannot (id &optional buffer) + (pdf-annot-create + (pdf-info-getannot id buffer) + buffer)) + +(defun pdf-annot-get (a property &optional default) + "Get annotation A's value of PROPERTY. + +Return DEFAULT, if value is nil." + (or (cdr (assq property a)) default)) + +(defun pdf-annot-put (a property value) + "Set annotation A's PROPERTY to VALUE. + +Unless VALUE is `equal' to the current value, sets A's buffer's +modified flag and runs the hook `pdf-annot-modified-functions'. + +Signals an error, if PROPERTY is not modifiable. + +Returns the modified annotation." + + (declare (indent 2)) + (unless (equal value (pdf-annot-get a property)) + (unless (pdf-annot-property-modifiable-p a property) + (error "Property `%s' is read-only for this annotation" + property)) + (with-current-buffer (pdf-annot-get-buffer a) + (setq a (pdf-annot-create + (pdf-info-editannot + (pdf-annot-get-id a) + `((,property . ,value))))) + (set-buffer-modified-p t) + (pdf-annot-run-modified-hooks :change a))) + a) + +(defun pdf-annot-run-modified-hooks (&optional operation &rest annotations) + "Run `pdf-annot-modified-functions' using OPERATION on ANNOTATIONS. + +OPERATION should be one of nil, :change, :insert or :delete. If +nil, annotations should be empty. + +Redisplay modified pages. + +If `pdf-annot-inhibit-modification-hooks' in non-nil, this just +saves ANNOTATIONS and does not call the hooks until later, when +the variable is nil and this function is called again." + + (unless (memq operation '(nil :insert :change :delete)) + (error "Invalid operation: %s" operation)) + (when (and (null operation) annotations) + (error "Missing operation argument")) + + (when operation + (let ((list (plist-get pdf-annot-delayed-modified-annotations operation))) + (dolist (a annotations) + (cl-pushnew a list :test 'pdf-annot-equal)) + (setq pdf-annot-delayed-modified-annotations + (plist-put pdf-annot-delayed-modified-annotations + operation list)))) + (unless pdf-annot-inhibit-modification-hooks + (let* ((changed (plist-get pdf-annot-delayed-modified-annotations :change)) + (inserted (mapcar (lambda (a) + (or (car (cl-member a changed :test 'pdf-annot-equal)) + a)) + (plist-get pdf-annot-delayed-modified-annotations :insert))) + (deleted (plist-get pdf-annot-delayed-modified-annotations :delete)) + (union (cl-union (cl-union changed inserted :test 'pdf-annot-equal) + deleted :test 'pdf-annot-equal)) + (closure (lambda (arg) + (cl-ecase arg + (:inserted (copy-sequence inserted)) + (:changed (copy-sequence changed)) + (:deleted (copy-sequence deleted)) + (t (copy-sequence union)) + (nil nil)))) + (pages (mapcar (lambda (a) (pdf-annot-get a 'page)) union))) + (when union + (unwind-protect + (run-hook-with-args + 'pdf-annot-modified-functions closure) + (setq pdf-annot-delayed-modified-annotations nil) + (apply 'pdf-view-redisplay-pages pages)))))) + +(defun pdf-annot-equal (a1 a2) + "Return non-nil, if annotations A1 and A2 are equal. + +Two annotations are equal, if they belong to the same buffer and +have identical id properties." + (and (eq (pdf-annot-get-buffer a1) + (pdf-annot-get-buffer a2)) + (eq (pdf-annot-get-id a1) + (pdf-annot-get-id a2)))) + +(defun pdf-annot-get-buffer (a) + "Return annotation A's buffer." + (pdf-annot-get a 'buffer)) + +(defun pdf-annot-get-id (a) + "Return id property of annotation A." + (pdf-annot-get a 'id)) + +(defun pdf-annot-get-type (a) + "Return type property of annotation A." + (pdf-annot-get a 'type)) + +(defun pdf-annot-get-display-edges (a) + "Return a list of EDGES used for display for annotation A. + +This returns a list of \(LEFT TOP RIGHT BOT\) demarking the +rectangles of the page where A is rendered." + + (or (pdf-annot-get a 'markup-edges) + (list (pdf-annot-get a 'edges)))) + +(defun pdf-annot-delete (a) + "Delete annotation A. + +Sets A's buffer's modified flag and runs the hook +`pdf-annot-modified-functions'. + +This function always returns nil." + (interactive + (list (pdf-annot-read-annotation + "Click on the annotation you wish to delete"))) + (with-current-buffer (pdf-annot-get-buffer a) + (pdf-info-delannot + (pdf-annot-get-id a)) + (set-buffer-modified-p t) + (pdf-annot-run-modified-hooks :delete a)) + (when (called-interactively-p 'any) + (message "Annotation deleted")) + nil) + +(defun pdf-annot-text-annotation-p (a) + (eq 'text (pdf-annot-get a 'type))) + +(defun pdf-annot-markup-annotation-p (a) + (not (null + (memq (pdf-annot-get a 'type) + pdf-annot-markup-annotation-types)))) + +(defun pdf-annot-property-modifiable-p (a property) + (or (memq property '(edges color flags contents)) + (and (pdf-annot-markup-annotation-p a) + (memq property '(label opacity popup popup-is-open))) + (and (pdf-annot-text-annotation-p a) + (memq property '(icon is-open))))) + +(defun pdf-annot-activate-annotation (a) + (or (run-hook-with-args-until-success + 'pdf-annot-activate-handler-functions + a) + (pdf-annot-default-activate-handler a))) + +(defun pdf-annot-default-activate-handler (a) + (cond + ((pdf-annot-has-attachment-p a) + (pdf-annot-pop-to-attachment a)) + (t (pdf-annot-edit-contents a)))) + + +;; * ================================================================== * +;; * Handling attachments +;; * ================================================================== * + +(defun pdf-annot-has-attachment-p (a) + "Return non-nil if annotation A's has data attached." + (eq 'file (pdf-annot-get a 'type))) + +(defun pdf-annot-get-attachment (a &optional do-save) + "Retrieve annotation A's attachment. + +The DO-SAVE argument is given to +`pdf-info-getattachment-from-annot', which see." + (unless (pdf-annot-has-attachment-p a) + (error "Annotation has no data attached: %s" a)) + (pdf-info-getattachment-from-annot + (pdf-annot-get-id a) + do-save + (pdf-annot-get-buffer a))) + +(defun pdf-annot-attachment-base-directory () + "Return the base directory for saving attachments." + (let ((dir (pdf-util-expand-file-name "attachments"))) + (unless (file-exists-p dir) + (make-directory dir)) + dir)) + +(defun pdf-annot-attachment-delete-base-directory () + "Delete all saved attachment files of the current buffer." + (setq pdf-annot--attachment-file-alist nil) + (delete-directory (pdf-annot-attachment-base-directory) t)) + +(defun pdf-annot-attachment-unique-filename (attachment) + "Return a unique absolute filename for ATTACHMENT." + (let* ((filename (or (cdr (assq 'filename attachment)) + "attachment")) + (id (cdr (assq 'id attachment))) + (unique + (or (cdr (assoc id pdf-annot--attachment-file-alist)) + (let* ((sans-ext + (expand-file-name + (concat (file-name-as-directory ".") + (file-name-sans-extension filename)) + (pdf-annot-attachment-base-directory))) + (ext (file-name-extension filename)) + (newname (concat sans-ext "." ext)) + (i 0)) + (while (rassoc newname pdf-annot--attachment-file-alist) + (setq newname (format "%s-%d.%s" sans-ext (cl-incf i) ext))) + (push (cons id newname) pdf-annot--attachment-file-alist) + newname))) + (directory (file-name-directory unique))) + (unless (file-exists-p directory) + (make-directory directory t)) + unique)) + + +(defun pdf-annot-attachment-save (attachment &optional regenerate-p) + "Save ATTACHMENT's data to a unique filename and return it's name. + +If REGENERATE-P is non-nil, copy attachment's file even if the +copy already exists. + +Signal an error, if ATTACHMENT has no, or a non-existing, `file' +property, i.e. it was retrieved with an unset do-save argument. +See `pdf-info-getattachments'" + + (let ((datafile (cdr (assq 'file attachment)))) + (unless (and datafile + (file-exists-p datafile)) + (error "Attachment's file property is invalid")) + (let* ((filename + (pdf-annot-attachment-unique-filename attachment))) + (when (or regenerate-p + (not (file-exists-p filename))) + (copy-file datafile filename nil nil t t)) + filename))) + +(defun pdf-annot-find-attachment-noselect (a) + "Find annotation A's attachment in a buffer, without selecting it. + +Signals an error, if A has no data attached." + (let ((attachment (pdf-annot-get-attachment a t))) + (unwind-protect + (find-file-noselect + (pdf-annot-attachment-save attachment)) + (let ((tmpfile (cdr (assq 'file attachment)))) + (when (and tmpfile + (file-exists-p tmpfile)) + (delete-file tmpfile)))))) + +(defun pdf-annot-attachment-dired (&optional regenerate-p) + "List all attachments in a dired buffer. + +If REGENERATE-P is non-nil, create attachment's files even if +they already exist. Interactively REGENERATE-P is non-nil if a +prefix argument was given. + +Return the dired buffer." + (interactive (list current-prefix-arg)) + (let ((attachments (pdf-info-getattachments t))) + (unwind-protect + (progn + (dolist (a (pdf-annot-getannots nil 'file)) + (push (pdf-annot-get-attachment a t) + attachments )) + (dolist (att attachments) + (pdf-annot-attachment-save att regenerate-p)) + (unless attachments + (error "Document has no data attached")) + (dired (pdf-annot-attachment-base-directory))) + (dolist (att attachments) + (let ((tmpfile (cdr (assq 'file att)))) + (when (and tmpfile (file-exists-p tmpfile)) + (delete-file tmpfile))))))) + +(defun pdf-annot-display-attachment (a &optional display-action select-window-p) + "Display file annotation A's data in a buffer. + +DISPLAY-ACTION should be a valid `display-buffer' action. If +nil, `pdf-annot-attachment-display-buffer-action' is used. + +Select the window, if SELECT-WINDOW-P is non-nil. + +Return the window attachment is displayed in." + + (interactive + (list (pdf-annot-read-annotation + "Select a file annotation by clicking on it"))) + (let* ((buffer (pdf-annot-find-attachment-noselect a)) + (window (display-buffer + buffer (or display-action + pdf-annot-attachment-display-buffer-action)))) + (when select-window-p + (select-window window)) + window)) + +(defun pdf-annot-pop-to-attachment (a) + "Display annotation A's attachment in a window and select it." + (interactive + (list (pdf-annot-read-annotation + "Select a file annotation by clicking on it"))) + (pdf-annot-display-attachment a nil t)) + + +;; * ================================================================== * +;; * Interfacing with the display +;; * ================================================================== * + +(defun pdf-annot-image-position (a &optional image-size) + "Return the position of annotation A in image coordinates. + +IMAGE-SIZE should be a cons \(WIDTH . HEIGHT\) and defaults to +the page-image of the selected window." + + (unless image-size + (pdf-util-assert-pdf-window) + (setq image-size (pdf-view-image-size))) + (let ((e (pdf-util-scale + (pdf-annot-get a 'edges) + image-size))) + (pdf-util-with-edges (e) + `(,e-left . ,e-top)))) + +(defun pdf-annot-image-set-position (a x y &optional image-size) + "Set annotation A's position to X,Y in image coordinates. + +See `pdf-annot-image-position' for IMAGE-SIZE." + + (unless image-size + (pdf-util-assert-pdf-window) + (setq image-size (pdf-view-image-size))) + (let* ((edges (pdf-annot-get a 'edges)) + (x (/ x (float (car image-size)))) + (y (/ y (float (cdr image-size))))) + (pdf-util-with-edges (edges) + (let* ((w edges-width) + (h edges-height) + (x (max 0 (min x (- 1 w)))) + (y (max 0 (min y (- 1 h))))) + (pdf-annot-put a 'edges + (list x y -1 -1)))))) + +(defun pdf-annot-image-size (a &optional image-size) + "Return the size of annotation A in image coordinates. + +Returns \(WIDTH . HEIGHT\). + +See `pdf-annot-image-position' for IMAGE-SIZE." + (unless image-size + (pdf-util-assert-pdf-window) + (setq image-size (pdf-view-image-size))) + (let ((edges (pdf-util-scale + (pdf-annot-get a 'edges) image-size))) + (pdf-util-with-edges (edges) + (cons edges-width edges-height)))) + +(defun pdf-annot-image-set-size (a &optional width height image-size) + "Set annotation A's size in image to WIDTH and/or HEIGHT. + +See `pdf-annot-image-position' for IMAGE-SIZE." + (unless image-size + (pdf-util-assert-pdf-window) + (setq image-size (pdf-view-image-size))) + (let* ((edges (pdf-annot-get a 'edges)) + (w (and width + (/ width (float (car image-size))))) + (h (and height + (/ height (float (cdr image-size)))))) + (pdf-util-with-edges (edges) + (pdf-annot-put a 'edges + (list edges-left + edges-top + (if w (+ edges-left w) edges-right) + (if h (+ edges-top h) edges-bot)))))) + +(defun pdf-annot-at-position (pos) + "Return annotation at POS in the selected window. + +POS should be an absolute image position as a cons \(X . Y\). +Alternatively POS may also be an event position, in which case +`posn-window' and `posn-object-x-y' is used to find the image +position. + +Return nil, if no annotation was found." + (let (window) + (when (posnp pos) + (setq window (posn-window pos) + pos (posn-object-x-y pos))) + (save-selected-window + (when window (select-window window 'norecord)) + (let* ((annots (pdf-annot-getannots (pdf-view-current-page))) + (size (pdf-view-image-size)) + (rx (/ (car pos) (float (car size)))) + (ry (/ (cdr pos) (float (cdr size)))) + (rpos (cons rx ry))) + (or (cl-some (lambda (a) + (and (cl-some + (lambda (e) + (pdf-util-edges-inside-p e rpos)) + (pdf-annot-get-display-edges a)) + a)) + annots) + (error "No annotation at this position")))))) + +(defun pdf-annot-mouse-move (event &optional annot) + "Start moving an annotation at EVENT's position. + +EVENT should be a mouse event originating the request and is used +as a reference point. + +ANNOT is the annotation to operate on and defaults to the +annotation at EVENT's start position. + +This function does not return until the operation is completed, +i.e. a non mouse-movement event is read." + + (interactive "@e") + (pdf-util-assert-pdf-window (posn-window (event-start event))) + (select-window (posn-window (event-start event))) + (let* ((mpos (posn-object-x-y (event-start event))) + (a (or annot + (pdf-annot-at-position mpos)))) + (unless a + (error "No annotation at this position: %s" mpos)) + (let* ((apos (pdf-annot-image-position a)) + (offset (cons (- (car mpos) (car apos)) + (- (cdr mpos) (cdr apos)))) + (window (selected-window)) + make-pointer-invisible) + (when (pdf-util-track-mouse-dragging (ev 0.1) + (when (and (eq window (posn-window (event-start ev))) + (eq 'image (car-safe (posn-object (event-start ev))))) + (let ((pdf-view-inhibit-hotspots t) + (pdf-annot-inhibit-modification-hooks t) + (pdf-cache-image-inihibit t) + (xy (posn-object-x-y (event-start ev)))) + (pdf-annot-image-set-position + a (- (car xy) (car offset)) + (- (cdr xy) (cdr offset))) + (pdf-view-redisplay)))) + (pdf-annot-run-modified-hooks))) + nil)) + +(defun pdf-annot-hotspot-function (page size) + "Create image hotspots for page PAGE of size SIZE." + (apply 'nconc (mapcar (lambda (a) + (unless (eq (pdf-annot-get a 'type) + 'link) + (pdf-annot-create-hotspots a size))) + (pdf-annot-getannots page)))) + +(defun pdf-annot-create-hotspots (a size) + "Return a list of image hotspots for annotation A." + (let ((id (pdf-annot-get-id a)) + (edges (pdf-util-scale + (pdf-annot-get-display-edges a) + size 'round)) + (moveable-p (memq (pdf-annot-get a 'type) + '(file text))) + hotspots) + (dolist (e edges) + (pdf-util-with-edges (e) + (push `((rect . ((,e-left . ,e-top) . (,e-right . ,e-bot))) + ,id + (pointer + hand + help-echo + ,(pdf-annot-print-annotation a))) + hotspots))) + (pdf-annot-create-hotspot-binding id moveable-p a) + hotspots)) + +;; FIXME: Define a keymap as a template for this. Much cleaner. +(defun pdf-annot-create-hotspot-binding (id moveable-p annotation) + ;; Activating + (local-set-key + (vector id 'mouse-1) + (lambda () + (interactive) + (pdf-annot-activate-annotation annotation))) + ;; Move + (when moveable-p + (local-set-key + (vector id 'down-mouse-1) + (lambda (ev) + (interactive "@e") + (pdf-annot-mouse-move ev annotation)))) + ;; Context Menu + (local-set-key + (vector id 'down-mouse-3) + (lambda () + (interactive "@") + (popup-menu (pdf-annot-create-context-menu annotation)))) + ;; Everything else + (local-set-key + (vector id t) + 'pdf-util-image-map-mouse-event-proxy)) + +(defun pdf-annot-show-annotation (a &optional highlight-p window) + "Make annotation A visible. + +Turn to A's page in WINDOW, and scroll it if necessary. + +If HIGHLIGHT-P is non-nil, visually distinguish annotation A from +other annotations." + + (save-selected-window + (when window (select-window window 'norecord)) + (pdf-util-assert-pdf-window) + (let ((page (pdf-annot-get a 'page)) + (size (pdf-view-image-size))) + (unless (= page (pdf-view-current-page)) + (pdf-view-goto-page page)) + (let ((edges (pdf-annot-get-display-edges a))) + (when highlight-p + (pdf-view-display-image + (pdf-view-create-image + (pdf-cache-renderpage-highlight + page (car size) + `("white" "steel blue" 0.35 ,@edges)) + :map (pdf-view-apply-hotspot-functions + window page size) + :width (car size)))) + (pdf-util-scroll-to-edges + (pdf-util-scale-relative-to-pixel (car edges))))))) + +(defun pdf-annot-read-annotation (&optional prompt) + "Let the user choose a annotation a mouse click using PROMPT." + (pdf-annot-at-position + (pdf-util-read-image-position + (or prompt "Choose a annotation by clicking on it")))) + + +;; * ================================================================== * +;; * Creating annotations +;; * ================================================================== * + +(defun pdf-annot-add-annotation (type edges &optional property-alist page) + "Creates and adds a new annotation of type TYPE to the document. + +TYPE determines the kind of annotation to add and maybe one of +`text', `squiggly', `underline', `strike-out' or `highlight'. + +EDGES determines where the annotation will appear on the page. +If type is `text', this should be a single list of \(LEFT TOP +RIGHT BOT\). Though, in this case only LEFT and TOP are used, +since the size of text annotations is fixed. Otherwise EDGES may +be a list of such elements. All values should be image relative +coordinates, i.e. in the range \[0;1\]. + +PROPERTY-ALIST is a list of annotation properties, which will be +put on the created annotation. + +PAGE determines the page of the annotation. It defaults to the +page currently displayed in the selected window. + +Signal an error, if PROPERTY-ALIST contains non-modifiable +properties or PAGE is nil and the selected window does not +display a PDF document or creating annotations of type TYPE is +not supported. + +Set buffers modified flag and calls +`pdf-annot-activate-annotation' if +`pdf-annot-activate-created-annotations' is non-nil. + +Return the new annotation." + + (unless (memq type (pdf-info-creatable-annotation-types)) + (error "Unsupported annotation type: %s" type)) + (unless page + (pdf-util-assert-pdf-window) + (setq page (pdf-view-current-page))) + (unless (consp (car-safe edges)) + (setq edges (list edges))) + (when (and (eq type 'text) + (> (length edges) 1)) + (error "Edges argument should be a single edge-list for text annotations")) + (let* ((a (apply 'pdf-info-addannot + page + (if (eq type 'text) + (car edges) + (apply #'pdf-util-edges-union + (apply #'append + (mapcar + (lambda (e) + (pdf-info-getselection page e)) + edges)))) + type + nil + (if (not (eq type 'text)) edges))) + (id (pdf-annot-get-id a))) + (when property-alist + (condition-case err + (setq a (pdf-info-editannot id property-alist)) + (error + (pdf-info-delannot id) + (signal (car err) (cdr err))))) + (setq a (pdf-annot-create a)) + (set-buffer-modified-p t) + (pdf-annot-run-modified-hooks :insert a) + (when pdf-annot-activate-created-annotations + (pdf-annot-activate-annotation a)) + a)) + +(defun pdf-annot-add-text-annotation (pos &optional icon property-alist) + "Add a new text annotation at POS in the selected window. + +POS should be a image position object or a cons \(X . Y\), both +being image coordinates. + +ICON determines how the annotation is displayed and should be +listed in `pdf-annot-standard-text-icons'. Any other value is ok +as well, but will render the annotation invisible. + +Adjust X and Y accordingly, if the position would render the +annotation off-page. + +Merge ICON as a icon property with PROPERTY-ALIST and +`pdf-annot-default-text-annotation-properties' and apply the +result to the created annotation. + +See also `pdf-annot-add-annotation'. + +Return the new annotation." + + (interactive + (let* ((posn (pdf-util-read-image-position + "Click where a new text annotation should be added ...")) + (window (posn-window posn))) + (select-window window) + (list posn))) + (pdf-util-assert-pdf-window) + (when (posnp pos) + (setq pos (posn-object-x-y pos))) + (let ((isize (pdf-view-image-size)) + (x (car pos)) + (y (cdr pos))) + (unless (and (>= x 0) + (< x (car isize))) + (signal 'args-out-of-range (list pos))) + (unless (and (>= y 0) + (< y (cdr isize))) + (signal 'args-out-of-range (list pos))) + (let ((size (pdf-util-scale-points-to-pixel + pdf-annot-text-annotation-size 'round))) + (setcar size (min (car size) (car isize))) + (setcdr size (min (cdr size) (cdr isize))) + (cl-decf x (max 0 (- (+ x (car size)) (car isize)))) + (cl-decf y (max 0 (- (+ y (cdr size)) (cdr isize)))) + (pdf-annot-add-annotation + 'text (pdf-util-scale-pixel-to-relative + (list x y -1 -1)) + (pdf-annot-merge-alists + (and icon `((icon . ,icon))) + property-alist + pdf-annot-default-text-annotation-properties + (cdr (assq 'text pdf-annot-default-annotation-properties)) + (cdr (assq t pdf-annot-default-annotation-properties)) + `((color . ,(car pdf-annot-color-history)))))))) + +(defun pdf-annot-mouse-add-text-annotation (ev) + (interactive "@e") + (pdf-annot-add-text-annotation + (if (eq (car-safe ev) + 'menu-bar) + (let (echo-keystrokes) + (message nil) + (pdf-util-read-image-position + "Click where a new text annotation should be added ...")) + (event-start ev)))) + +(defun pdf-annot-add-markup-annotation (list-of-edges type &optional color + property-alist) + "Add a new markup annotation in the selected window. + +LIST-OF-EDGES determines the marked up area and should be a list +of \(LEFT TOP RIGHT BOT\), each value a relative coordinate. + +TYPE should be one of `squiggly', `underline', `strike-out' or +`highlight'. + +Merge COLOR as a color property with PROPERTY-ALIST and +`pdf-annot-default-markup-annotation-properties' and apply the +result to the created annotation. + +See also `pdf-annot-add-annotation'. + +Return the new annotation." + (interactive + (list (pdf-view-active-region t) + (let ((type (completing-read "Markup type (default highlight): " + '("squiggly" "highlight" "underline" "strike-out") + nil t))) + (if (equal type "") 'highlight (intern type))) + (pdf-annot-read-color))) + (pdf-util-assert-pdf-window) + (pdf-annot-add-annotation + type + list-of-edges + (pdf-annot-merge-alists + (and color `((color . ,color))) + property-alist + pdf-annot-default-markup-annotation-properties + (cdr (assq type pdf-annot-default-annotation-properties)) + (cdr (assq t pdf-annot-default-annotation-properties)) + (when pdf-annot-color-history + `((color . ,(car pdf-annot-color-history)))) + '((color . "#ffff00"))) + (pdf-view-current-page))) + +(defun pdf-annot-add-squiggly-markup-annotation (list-of-edges + &optional color property-alist) + "Add a new squiggly annotation in the selected window. + +See also `pdf-annot-add-markup-annotation'." + (interactive (list (pdf-view-active-region t))) + (pdf-annot-add-markup-annotation list-of-edges 'squiggly color property-alist)) + +(defun pdf-annot-add-underline-markup-annotation (list-of-edges + &optional color property-alist) + "Add a new underline annotation in the selected window. + +See also `pdf-annot-add-markup-annotation'." + (interactive (list (pdf-view-active-region t))) + (pdf-annot-add-markup-annotation list-of-edges 'underline color property-alist)) + +(defun pdf-annot-add-strikeout-markup-annotation (list-of-edges + &optional color property-alist) + "Add a new strike-out annotation in the selected window. + +See also `pdf-annot-add-markup-annotation'." + (interactive (list (pdf-view-active-region t))) + (pdf-annot-add-markup-annotation list-of-edges 'strike-out color property-alist)) + +(defun pdf-annot-add-highlight-markup-annotation (list-of-edges + &optional color property-alist) + "Add a new highlight annotation in the selected window. + +See also `pdf-annot-add-markup-annotation'." + (interactive (list (pdf-view-active-region t))) + (pdf-annot-add-markup-annotation list-of-edges 'highlight color property-alist)) + +(defun pdf-annot-read-color (&optional prompt) + "Read and return a color using PROMPT. + +Offer `pdf-annot-color-history' as default values." + (let* ((defaults (append + (delq nil + (list + (cdr (assq 'color + pdf-annot-default-markup-annotation-properties)) + (cdr (assq 'color + pdf-annot-default-text-annotation-properties)))) + pdf-annot-color-history)) + (prompt + (format "%s%s: " + (or prompt "Color") + (if defaults (format " (default %s)" (car defaults)) ""))) + (current-completing-read-function completing-read-function) + (completing-read-function + (lambda (prompt collection &optional predicate require-match + initial-input _hist _def inherit-input-method) + (funcall current-completing-read-function + prompt collection predicate require-match + initial-input 'pdf-annot-color-history + defaults + inherit-input-method)))) + (read-color prompt))) + +(defun pdf-annot-merge-alists (&rest alists) + "Merge ALISTS into a single one. + +Suppresses successive duplicate entries of keys after the first +occurrence in ALISTS." + + (let (merged) + (dolist (elt (apply 'append alists)) + (unless (assq (car elt) merged) + (push elt merged))) + (nreverse merged))) + + + +;; * ================================================================== * +;; * Displaying annotation contents +;; * ================================================================== * + +(defun pdf-annot-print-property (a property) + "Pretty print annotation A's property PROPERTY." + (let ((value (pdf-annot-get a property))) + (cl-case property + (color + (propertize (or value "") + 'face (and value + `(:background ,value)))) + ((created modified) + (let ((date value)) + (if (null date) + "No date" + (current-time-string date)))) + ;; print verbatim + (subject + (or value "No subject")) + (opacity + (let ((opacity (or value 1.0))) + (format "%d%%" (round (* 100 opacity))))) + (t (format "%s" (or value "")))))) + +(defun pdf-annot-print-annotation (a) + "Pretty print annotation A." + (or (run-hook-with-args-until-success + 'pdf-annot-print-annotation-functions a) + (pdf-annot-print-annotation-default a))) + +(defun pdf-annot-print-annotation-default (a) + "Default pretty printer for annotation A. + +The result consists of a header (as printed with +`pdf-annot-print-annotation-header') a newline and A's contents +property." + (concat + (pdf-annot-print-annotation-header a) + "\n" + (pdf-annot-get a 'contents))) + +(defun pdf-annot-print-annotation-header (a) + "Emit a suitable header string for annotation A." + (let ((header + (cond + ((eq 'file (pdf-annot-get a 'type)) + (let ((att (pdf-annot-get-attachment a))) + (format "File attachment `%s' of %s" + (or (cdr (assq 'filename att)) "unnamed") + (if (cdr (assq 'size att)) + (format "size %s" (file-size-human-readable + (cdr (assq 'size att)))) + "unknown size")))) + (t + (format "%s" + (mapconcat + 'identity + (mapcar + (lambda (property) + (pdf-annot-print-property + a property)) + `(subject + label + modified)) + ";")))))) + (setq header (propertize header 'face 'header-line + 'intangible t 'read-only t)) + ;; This `trick' makes the face apply in a tooltip. + (propertize header 'display header))) + +(defun pdf-annot-print-annotation-latex-maybe (a) + "Maybe print annotation A's content as a LaTeX fragment. + +See `pdf-annot-latex-string-predicate'." + (when (and (functionp pdf-annot-latex-string-predicate) + (funcall pdf-annot-latex-string-predicate + (pdf-annot-get a 'contents))) + (pdf-annot-print-annotation-latex a))) + +(defun pdf-annot-print-annotation-latex (a) + "Print annotation A's content as a LaTeX fragment. + +This compiles A's contents as a LaTeX fragment and puts the +resulting image as a display property on the contents, prefixed +by a header." + + (let (tempfile) + (unwind-protect + (with-current-buffer (pdf-annot-get-buffer a) + (let* ((page (pdf-annot-get a 'page)) + (header (pdf-annot-print-annotation-header a)) + (contents (pdf-annot-get a 'contents)) + (hash (sxhash (format + "pdf-annot-print-annotation-latex%s%s%s" + page header contents))) + (data (pdf-cache-lookup-image page 0 nil hash)) + (org-format-latex-header + pdf-annot-latex-header) + (temporary-file-directory + (pdf-util-expand-file-name "pdf-annot-print-annotation-latex"))) + (unless (file-directory-p temporary-file-directory) + (make-directory temporary-file-directory)) + (unless data + (setq tempfile (make-temp-file "pdf-annot" nil ".png")) + ;; FIXME: Why is this with-temp-buffer needed (which it is) ? + (with-temp-buffer + (org-create-formula-image + contents tempfile org-format-latex-options t)) + (setq data (pdf-util-munch-file tempfile)) + (if (and (> (length data) 3) + (equal (substring data 1 4) + "PNG")) + (pdf-cache-put-image page 0 data hash) + (setq data nil))) + (concat + header + "\n" + (if data + (propertize + contents 'display (pdf-view-create-image data)) + (propertize + contents + 'display + (concat + (propertize "Failed to compile latex fragment\n" + 'face 'error) + contents)))))) + (when (and tempfile + (file-exists-p tempfile)) + (delete-file tempfile))))) + + +;; * ================================================================== * +;; * Editing annotation contents +;; * ================================================================== * + +(defvar-local pdf-annot-edit-contents--annotation nil) +(put 'pdf-annot-edit-contents--annotation 'permanent-local t) +(defvar-local pdf-annot-edit-contents--buffer nil) + +(defcustom pdf-annot-edit-contents-setup-function + (lambda (a) + (let ((mode (if (funcall pdf-annot-latex-string-predicate + (pdf-annot-get a 'contents)) + 'latex-mode + 'text-mode))) + (unless (derived-mode-p mode) + (funcall mode)))) + "A function for setting up, e.g. the major-mode, of the edit buffer. + +The function receives one argument, the annotation whose contents +is about to be edited in this buffer. + +The default value turns on `latex-mode' if +`pdf-annot-latex-string-predicate' returns non-nil on the +annotation's contents and otherwise `text-mode'. " + :group 'pdf-annot + :type 'function) + +(defcustom pdf-annot-edit-contents-display-buffer-action + '((display-buffer-reuse-window + display-buffer-split-below-and-attach) + (inhibit-same-window . t) + (window-height . 0.25)) + "Display action when showing the edit buffer." + :group 'pdf-annot + :type display-buffer--action-custom-type) + +(defvar pdf-annot-edit-contents-minor-mode-map + (let ((kmap (make-sparse-keymap))) + (set-keymap-parent kmap text-mode-map) + (define-key kmap (kbd "C-c C-c") 'pdf-annot-edit-contents-commit) + (define-key kmap (kbd "C-c C-q") 'pdf-annot-edit-contents-abort) + kmap)) + +(define-minor-mode pdf-annot-edit-contents-minor-mode + "Active when editing the contents of annotations." + :group 'pdf-annot + (when pdf-annot-edit-contents-minor-mode + (message "%s" + (substitute-command-keys + "Press \\[pdf-annot-edit-contents-commit] to commit your changes, \\[pdf-annot-edit-contents-abort] to abandon them.")))) + +(put 'pdf-annot-edit-contents-minor-mode 'permanent-local t) + +;; FIXME: Document pdf-annot-edit-* functions below. +(defun pdf-annot-edit-contents-finalize (do-save &optional do-kill) + (when (buffer-modified-p) + (cond + ((eq do-save 'ask) + (save-window-excursion + (display-buffer (current-buffer) nil (selected-frame)) + (when (y-or-n-p "Save changes to this annotation ?") + (pdf-annot-edit-contents-save-annotation)))) + (do-save + (pdf-annot-edit-contents-save-annotation))) + (set-buffer-modified-p nil)) + (dolist (win (get-buffer-window-list)) + (quit-window do-kill win))) + +(defun pdf-annot-edit-contents-save-annotation () + (when pdf-annot-edit-contents--annotation + (pdf-annot-put pdf-annot-edit-contents--annotation + 'contents + (buffer-substring-no-properties (point-min) (point-max))) + (set-buffer-modified-p nil))) + +(defun pdf-annot-edit-contents-commit () + (interactive) + (pdf-annot-edit-contents-finalize t)) + +(defun pdf-annot-edit-contents-abort () + (interactive) + (pdf-annot-edit-contents-finalize nil t)) + +(defun pdf-annot-edit-contents-noselect (a) + (with-current-buffer (pdf-annot-get-buffer a) + (when (and (buffer-live-p pdf-annot-edit-contents--buffer) + (not (eq a pdf-annot-edit-contents--annotation))) + (with-current-buffer pdf-annot-edit-contents--buffer + (pdf-annot-edit-contents-finalize 'ask))) + (unless (buffer-live-p pdf-annot-edit-contents--buffer) + (setq pdf-annot-edit-contents--buffer + (with-current-buffer (get-buffer-create + (format "*Edit Annotation %s*" + (buffer-name))) + (pdf-annot-edit-contents-minor-mode 1) + (current-buffer)))) + (with-current-buffer pdf-annot-edit-contents--buffer + (let ((inhibit-read-only t)) + (erase-buffer) + (save-excursion (insert (pdf-annot-get a 'contents))) + (set-buffer-modified-p nil)) + (setq pdf-annot-edit-contents--annotation a) + (funcall pdf-annot-edit-contents-setup-function a) + (current-buffer)))) + +(defun pdf-annot-edit-contents (a) + (select-window + (display-buffer + (pdf-annot-edit-contents-noselect a) + pdf-annot-edit-contents-display-buffer-action))) + +(defun pdf-annot-edit-contents-mouse (ev) + (interactive "@e") + (let* ((pos (posn-object-x-y (event-start ev))) + (a (and pos (pdf-annot-at-position pos)))) + (unless a + (error "No annotation at this position")) + (pdf-annot-edit-contents a))) + + + +;; * ================================================================== * +;; * Listing annotations +;; * ================================================================== * + +(defcustom pdf-annot-list-display-buffer-action + '((display-buffer-reuse-window + display-buffer-pop-up-window) + (inhibit-same-window . t)) + "Display action used when displaying the list buffer." + :group 'pdf-annot + :type display-buffer--action-custom-type) + +(defcustom pdf-annot-list-format + '((page . 3) + (type . 10) + (label . 24) + (date . 24)) + "Annotation properties visible in the annotation list. + +It should be a list of \(PROPERTIZE. WIDTH\), where PROPERTY is a +symbol naming one of supported properties to list and WIDTH its +desired column-width. + +Currently supported properties are page, type, label, date and contents." + :type '(alist :key-type (symbol)) + :options '((page (integer :value 3 :tag "Column Width")) + (type (integer :value 10 :tag "Column Width" )) + (label (integer :value 24 :tag "Column Width")) + (date (integer :value 24 :tag "Column Width")) + (contents (integer :value 56 :tag "Column Width"))) + :group 'pdf-annot) + +(defcustom pdf-annot-list-highlight-type nil + "Whether to highlight \"Type\" column annotation list with annotation color." + :group 'pdf-annot + :type 'boolean) + +(defvar-local pdf-annot-list-buffer nil) + +(defvar-local pdf-annot-list-document-buffer nil) + +(defvar pdf-annot-list-mode-map + (let ((km (make-sparse-keymap))) + (define-key km (kbd "C-c C-f") 'pdf-annot-list-follow-minor-mode) + (define-key km (kbd "SPC") 'pdf-annot-list-display-annotation-from-id) + km)) + +(defun pdf-annot-property-completions (property) + "Return a list of completion candidates for annotation property PROPERTY. + +Return nil, if not available." + (cl-case property + (color (pdf-util-color-completions)) + (icon (copy-sequence pdf-annot-standard-text-icons)))) + +(defun pdf-annot-compare-annotations (a1 a2) + "Compare annotations A1 and A2. + +Return non-nil if A1's page is less than A2's one or if they +belong to the same page and A1 is displayed above/left of A2." + (let ((p1 (pdf-annot-get a1 'page)) + (p2 (pdf-annot-get a2 'page))) + (or (< p1 p2) + (and (= p1 p2) + (let ((e1 (pdf-util-scale + (car (pdf-annot-get-display-edges a1)) + '(1000 . 1000))) + (e2 (pdf-util-scale + (car (pdf-annot-get-display-edges a2)) + '(1000 . 1000)))) + (pdf-util-with-edges (e1 e2) + (or (< e1-top e2-top) + (and (= e1-top e2-top) + (<= e1-left e2-left))))))))) + +(defun pdf-annot-list-entries () + (unless (buffer-live-p pdf-annot-list-document-buffer) + (error "No PDF document associated with this buffer")) + (mapcar 'pdf-annot-list-create-entry + (sort (pdf-annot-getannots nil pdf-annot-list-listed-types + pdf-annot-list-document-buffer) + 'pdf-annot-compare-annotations))) + +(defun pdf-annot--make-entry-formatter (a) + (lambda (fmt) + (let ((entry-type (car fmt)) + (entry-width (cdr fmt)) + ;; Taken from css-mode.el + (contrasty-color + (lambda (name) + (if (> (color-distance name "black") 292485) + "black" "white"))) + (prune-newlines + (lambda (str) + (replace-regexp-in-string "\n" " " str t t)))) + (cl-ecase entry-type + (date (pdf-annot-print-property a 'modified)) + (page (pdf-annot-print-property a 'page)) + (label (funcall prune-newlines + (pdf-annot-print-property a 'label))) + (contents + (truncate-string-to-width + (funcall prune-newlines + (pdf-annot-print-property a 'contents)) + entry-width)) + (type + (let ((color (pdf-annot-get a 'color)) + (type (pdf-annot-print-property a 'type))) + (if pdf-annot-list-highlight-type + (propertize + type 'face + `(:background ,color + :foreground ,(funcall contrasty-color color))) + type))))))) + +(defun pdf-annot-list-create-entry (a) + "Create a `tabulated-list-entries' entry for annotation A." + (list (pdf-annot-get-id a) + (vconcat + (mapcar (pdf-annot--make-entry-formatter a) + pdf-annot-list-format)))) + +(define-derived-mode pdf-annot-list-mode tablist-mode "Annots" + (let* ((page-sorter + (lambda (a b) + (< (string-to-number (aref (cadr a) 0)) + (string-to-number (aref (cadr b) 0))))) + (format-generator + (lambda (format) + (let ((field (car format)) + (width (cdr format))) + (cl-case field + (page `("Pg." 3 ,page-sorter :read-only t :right-alight t)) + (t (list + (capitalize (symbol-name field)) + width t :read-only t))))))) + (setq tabulated-list-entries 'pdf-annot-list-entries + tabulated-list-format (vconcat + (mapcar + format-generator + pdf-annot-list-format)) + tabulated-list-padding 2)) + (set-keymap-parent pdf-annot-list-mode-map tablist-mode-map) + (use-local-map pdf-annot-list-mode-map) + (when (assq 'type pdf-annot-list-format) + (setq tablist-current-filter + `(not (== "Type" "link")))) + (tabulated-list-init-header)) + +(defun pdf-annot-list-annotations () + "List annotations in a dired like buffer. + +\\{pdf-annot-list-mode-map}" + (interactive) + (pdf-util-assert-pdf-buffer) + (let ((buffer (current-buffer))) + (with-current-buffer (get-buffer-create + (format "*%s's annots*" + (file-name-sans-extension + (buffer-name)))) + (delay-mode-hooks + (unless (derived-mode-p 'pdf-annot-list-mode) + (pdf-annot-list-mode)) + (setq pdf-annot-list-document-buffer buffer) + (tabulated-list-print) + (setq tablist-context-window-function + (lambda (id) (pdf-annot-list-context-function id buffer)) + tablist-operations-function 'pdf-annot-list-operation-function) + (let ((list-buffer (current-buffer))) + (with-current-buffer buffer + (setq pdf-annot-list-buffer list-buffer)))) + (run-mode-hooks) + (pop-to-buffer + (current-buffer) + pdf-annot-list-display-buffer-action) + (tablist-move-to-major-column) + (tablist-display-context-window)) + (add-hook 'pdf-info-close-document-hook + 'pdf-annot-list-update nil t) + (add-hook 'pdf-annot-modified-functions + 'pdf-annot-list-update nil t))) + +(defun pdf-annot-list-goto-annotation (a) + (with-current-buffer (pdf-annot-get-buffer a) + (unless (and (buffer-live-p pdf-annot-list-buffer) + (get-buffer-window pdf-annot-list-buffer)) + (pdf-annot-list-annotations)) + (with-selected-window (get-buffer-window pdf-annot-list-buffer) + (goto-char (point-min)) + (let ((id (pdf-annot-get-id a))) + (while (and (not (eobp)) + (not (eq id (tabulated-list-get-id)))) + (forward-line)) + (unless (eq id (tabulated-list-get-id)) + (error "Unable to find annotation")) + (when (invisible-p (point)) + (tablist-suspend-filter t)) + (tablist-move-to-major-column))))) + + +(defun pdf-annot-list-update (&optional _fn) + (when (buffer-live-p pdf-annot-list-buffer) + (with-current-buffer pdf-annot-list-buffer + (unless tablist-edit-column-minor-mode + (tablist-revert)) + (tablist-context-window-update)))) + +(defun pdf-annot-list-context-function (id buffer) + (with-current-buffer (get-buffer-create "*Contents*") + (set-window-buffer nil (current-buffer)) + (let ((inhibit-read-only t)) + (erase-buffer) + (when id + (save-excursion + (insert + (pdf-annot-print-annotation + (pdf-annot-getannot id buffer))))) + (read-only-mode 1)))) + +(defun pdf-annot-list-operation-function (op &rest args) + (cl-ecase op + (supported-operations '(delete find-entry)) + (delete + (cl-destructuring-bind (ids) + args + (when (buffer-live-p pdf-annot-list-document-buffer) + (with-current-buffer pdf-annot-list-document-buffer + (pdf-annot-with-atomic-modifications + (dolist (a (mapcar 'pdf-annot-getannot ids)) + (pdf-annot-delete a))))))) + (find-entry + (cl-destructuring-bind (id) + args + (unless (buffer-live-p pdf-annot-list-document-buffer) + (error "No PDF document associated with this buffer")) + (let* ((buffer pdf-annot-list-document-buffer) + (a (pdf-annot-getannot id buffer)) + (pdf-window (save-selected-window + (or (get-buffer-window buffer) + (display-buffer buffer)))) + window) + (with-current-buffer buffer + (pdf-annot-activate-annotation a) + (setq window (selected-window))) + ;; Make it so that quitting the edit window returns to the + ;; list window. + (unless (memq window (list (selected-window) pdf-window)) + (let* ((quit-restore + (window-parameter window 'quit-restore))) + (when quit-restore + (setcar (nthcdr 2 quit-restore) (selected-window)))))))))) + +(defvar pdf-annot-list-display-annotation--timer nil) + +(defun pdf-annot-list-display-annotation-from-id (id) + (interactive (list (tabulated-list-get-id))) + (when id + (unless (buffer-live-p pdf-annot-list-document-buffer) + (error "PDF buffer was killed")) + (when (timerp pdf-annot-list-display-annotation--timer) + (cancel-timer pdf-annot-list-display-annotation--timer)) + (setq pdf-annot-list-display-annotation--timer + (run-with-idle-timer 0.1 nil + (lambda (buffer a) + (when (buffer-live-p buffer) + (with-selected-window + (or (get-buffer-window buffer) + (display-buffer + buffer + '(nil (inhibit-same-window . t)))) + (pdf-annot-show-annotation a t)))) + pdf-annot-list-document-buffer + (pdf-annot-getannot id pdf-annot-list-document-buffer))))) + +(define-minor-mode pdf-annot-list-follow-minor-mode + "" + :group 'pdf-annot + (unless (derived-mode-p 'pdf-annot-list-mode) + (error "No in pdf-annot-list-mode.")) + (cond + (pdf-annot-list-follow-minor-mode + (add-hook 'tablist-selection-changed-functions + 'pdf-annot-list-display-annotation-from-id nil t) + (let ((id (tabulated-list-get-id))) + (when id + (pdf-annot-list-display-annotation-from-id id)))) + (t + (remove-hook 'tablist-selection-changed-functions + 'pdf-annot-list-display-annotation-from-id t)))) + +(provide 'pdf-annot) +;;; pdf-annot.el ends here diff --git a/elpa/pdf-tools-20211110.513/pdf-annot.elc b/elpa/pdf-tools-20211110.513/pdf-annot.elc Binary files differ. diff --git a/elpa/pdf-tools-20211110.513/pdf-cache.el b/elpa/pdf-tools-20211110.513/pdf-cache.el @@ -0,0 +1,458 @@ +;;; pdf-cache.el --- Cache time-critical or frequent epdfinfo queries. -*- lexical-binding:t -*- + +;; Copyright (C) 2013 Andreas Politz + +;; Author: Andreas Politz <politza@fh-trier.de> +;; Keywords: files, doc-view, pdf + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: +;; +;;; Code: +;; + +(require 'pdf-macs) +(require 'pdf-info) +(require 'pdf-util) + + +;; * ================================================================== * +;; * Customiazations +;; * ================================================================== * + +(defcustom pdf-cache-image-limit 64 + "Maximum number of cached PNG images per buffer." + :type 'integer + :group 'pdf-cache + :group 'pdf-view) + +(defcustom pdf-cache-prefetch-delay 0.5 + "Idle time in seconds before prefetching images starts." + :group 'pdf-view + :type 'number) + +(defcustom pdf-cache-prefetch-pages-function + 'pdf-cache-prefetch-pages-function-default + "A function returning a list of pages to be prefetched. + +It is called with no arguments in the PDF window and should +return a list of page-numbers, determining the pages that should +be prefetched and their order." + :group 'pdf-view + :type 'function) + + +;; * ================================================================== * +;; * Simple Value cache +;; * ================================================================== * + +(defvar-local pdf-cache--data nil) + +(defvar pdf-annot-modified-functions) + +(defun pdf-cache--initialize () + (unless pdf-cache--data + (setq pdf-cache--data (make-hash-table)) + (add-hook 'pdf-info-close-document-hook 'pdf-cache-clear-data nil t) + (add-hook 'pdf-annot-modified-functions + 'pdf-cache--clear-data-of-annotations + nil t))) + +(defun pdf-cache--clear-data-of-annotations (fn) + (apply 'pdf-cache-clear-data-of-pages + (mapcar (lambda (a) + (cdr (assq 'page a))) + (funcall fn t)))) + +(defun pdf-cache--data-put (key value &optional page) + "Put KEY with VALUE in the cache of PAGE, return value." + (pdf-cache--initialize) + (puthash page (cons (cons key value) + (assq-delete-all + key + (gethash page pdf-cache--data))) + pdf-cache--data) + value) + +(defun pdf-cache--data-get (key &optional page) + "Get value of KEY in the cache of PAGE. + +Returns a cons \(HIT . VALUE\), where HIT is non-nil if KEY was +stored previously for PAGE and VALUE it's value. Otherwise HIT +is nil and VALUE undefined." + (pdf-cache--initialize) + (let ((elt (assq key (gethash page pdf-cache--data)))) + (if elt + (cons t (cdr elt)) + (cons nil nil)))) + +(defun pdf-cache--data-clear (key &optional page) + (pdf-cache--initialize) + (puthash page + (assq-delete-all key (gethash page pdf-cache--data)) + pdf-cache--data) + nil) + +(defun pdf-cache-clear-data-of-pages (&rest pages) + (when pdf-cache--data + (dolist (page pages) + (remhash page pdf-cache--data)))) + +(defun pdf-cache-clear-data () + (interactive) + (when pdf-cache--data + (clrhash pdf-cache--data))) + +(defmacro define-pdf-cache-function (command &optional page-arg-p) + "Define a simple data cache function. + +COMMAND is the name of the command, e.g. number-of-pages. It +should have a corresponding pdf-info function. If PAGE-ARG-P is +non-nil, define a one-dimensional cache indexed by the page +number. Otherwise the value is constant for each document, like +e.g. number-of-pages. + +Both args are unevaluated." + + (let ((args (if page-arg-p (list 'page))) + (fn (intern (format "pdf-cache-%s" command))) + (ifn (intern (format "pdf-info-%s" command))) + (doc (format "Cached version of `pdf-info-%s', which see. + +Make sure, not to modify it's return value." command))) + `(defun ,fn ,args + ,doc + (let ((hit-value (pdf-cache--data-get ',command ,(if page-arg-p 'page)))) + (if (car hit-value) + (cdr hit-value) + (pdf-cache--data-put + ',command + ,(if page-arg-p + (list ifn 'page) + (list ifn)) + ,(if page-arg-p 'page))))))) + +(define-pdf-cache-function pagelinks t) +(define-pdf-cache-function number-of-pages) +;; The boundingbox may change if annotations change. +(define-pdf-cache-function boundingbox t) +(define-pdf-cache-function textregions t) +(define-pdf-cache-function pagesize t) + + +;; * ================================================================== * +;; * PNG image LRU cache +;; * ================================================================== * + +(defvar pdf-cache-image-inihibit nil + "Non-nil, if the image cache should be bypassed.") + +(defvar-local pdf-cache--image-cache nil) + +(defmacro pdf-cache--make-image (page width data hash) + `(list ,page ,width ,data ,hash)) +(defmacro pdf-cache--image/page (img) `(nth 0 ,img)) +(defmacro pdf-cache--image/width (img) `(nth 1 ,img)) +(defmacro pdf-cache--image/data (img) `(nth 2 ,img)) +(defmacro pdf-cache--image/hash (img) `(nth 3 ,img)) + +(defun pdf-cache--image-match (image page min-width &optional max-width hash) + "Match IMAGE with specs. + +IMAGE should be a list as created by `pdf-cache--make-image'. + +Return non-nil, if IMAGE's page is the same as PAGE, it's width +is at least MIN-WIDTH and at most MAX-WIDTH and it's stored +hash-value is `eql' to HASH." + (and (= (pdf-cache--image/page image) + page) + (or (null min-width) + (>= (pdf-cache--image/width image) + min-width)) + (or (null max-width) + (<= (pdf-cache--image/width image) + max-width)) + (eql (pdf-cache--image/hash image) + hash))) + +(defun pdf-cache-lookup-image (page min-width &optional max-width hash) + "Return PAGE's cached PNG data as a string or nil. + +Does not modify the cache. See also `pdf-cache-get-image'." + (let ((image (car (cl-member + (list page min-width max-width hash) + pdf-cache--image-cache + :test (lambda (spec image) + (apply 'pdf-cache--image-match image spec)))))) + (and image + (pdf-cache--image/data image)))) + +(defun pdf-cache-get-image (page min-width &optional max-width hash) + "Return PAGE's PNG data as a string. + +Return an image of at least MIN-WIDTH and, if non-nil, maximum +width MAX-WIDTH and `eql' hash value. + +Remember that image was recently used. + +Returns nil, if no matching image was found." + (let ((cache pdf-cache--image-cache) + image) + ;; Find it in the cache. + (while (and (setq image (pop cache)) + (not (pdf-cache--image-match + image page min-width max-width hash)))) + ;; Remove it and push it to the front. + (when image + (setq pdf-cache--image-cache + (cons image (delq image pdf-cache--image-cache))) + (pdf-cache--image/data image)))) + +(defun pdf-cache-put-image (page width data &optional hash) + "Cache image of PAGE with WIDTH, DATA and HASH. + +DATA should the string of a PNG image of width WIDTH and from +page PAGE in the current buffer. See `pdf-cache-get-image' for +the HASH argument. + +This function always returns nil." + (unless pdf-cache--image-cache + (add-hook 'pdf-info-close-document-hook 'pdf-cache-clear-images nil t) + (add-hook 'pdf-annot-modified-functions + 'pdf-cache--clear-images-of-annotations nil t)) + (push (pdf-cache--make-image page width data hash) + pdf-cache--image-cache) + ;; Forget old image(s). + (when (> (length pdf-cache--image-cache) + pdf-cache-image-limit) + (if (> pdf-cache-image-limit 1) + (setcdr (nthcdr (1- pdf-cache-image-limit) + pdf-cache--image-cache) + nil) + (setq pdf-cache--image-cache nil))) + nil) + +(defun pdf-cache-clear-images () + "Clear the image cache." + (setq pdf-cache--image-cache nil)) + +(defun pdf-cache-clear-images-if (fn) + "Remove images from the cache according to FN. + +FN should be function accepting 4 Arguments \(PAGE WIDTH DATA +HASH\). It should return non-nil, if the image should be removed +from the cache." + (setq pdf-cache--image-cache + (cl-remove-if + (lambda (image) + (funcall + fn + (pdf-cache--image/page image) + (pdf-cache--image/width image) + (pdf-cache--image/data image) + (pdf-cache--image/hash image))) + pdf-cache--image-cache))) + + +(defun pdf-cache--clear-images-of-annotations (fn) + (apply 'pdf-cache-clear-images-of-pages + (mapcar (lambda (a) + (cdr (assq 'page a))) + (funcall fn t)))) + +(defun pdf-cache-clear-images-of-pages (&rest pages) + (pdf-cache-clear-images-if + (lambda (page &rest _) (memq page pages)))) + +(defun pdf-cache-renderpage (page min-width &optional max-width) + "Render PAGE according to MIN-WIDTH and MAX-WIDTH. + +Return the PNG data of an image as a string, such that it's width +is at least MIN-WIDTH and, if non-nil, at most MAX-WIDTH. + +If such an image is not available in the cache, call +`pdf-info-renderpage' to create one." + (if pdf-cache-image-inihibit + (pdf-info-renderpage page min-width) + (or (pdf-cache-get-image page min-width max-width) + (let ((data (pdf-info-renderpage page min-width))) + (pdf-cache-put-image page min-width data) + data)))) + +(defun pdf-cache-renderpage-text-regions (page width single-line-p + &rest selection) + "Render PAGE according to WIDTH, SINGLE-LINE-P and SELECTION. + +See also `pdf-info-renderpage-text-regions' and +`pdf-cache-renderpage'." + (if pdf-cache-image-inihibit + (apply 'pdf-info-renderpage-text-regions + page width single-line-p nil selection) + (let ((hash (sxhash + (format "%S" (cons 'renderpage-text-regions + (cons single-line-p selection)))))) + (or (pdf-cache-get-image page width width hash) + (let ((data (apply 'pdf-info-renderpage-text-regions + page width single-line-p nil selection))) + (pdf-cache-put-image page width data hash) + data))))) + +(defun pdf-cache-renderpage-highlight (page width &rest regions) + "Highlight PAGE according to WIDTH and REGIONS. + +See also `pdf-info-renderpage-highlight' and +`pdf-cache-renderpage'." + (if pdf-cache-image-inihibit + (apply 'pdf-info-renderpage-highlight + page width nil regions) + (let ((hash (sxhash + (format "%S" (cons 'renderpage-highlight + regions))))) + (or (pdf-cache-get-image page width width hash) + (let ((data (apply 'pdf-info-renderpage-highlight + page width nil regions))) + (pdf-cache-put-image page width data hash) + data))))) + + +;; * ================================================================== * +;; * Prefetching images +;; * ================================================================== * + +(defvar-local pdf-cache--prefetch-pages nil + "Pages to be prefetched.") + +(defvar-local pdf-cache--prefetch-timer nil + "Timer used when prefetching images.") + +(define-minor-mode pdf-cache-prefetch-minor-mode + "Try to load images which will probably be needed in a while." + :group 'pdf-cache + (pdf-cache--prefetch-cancel) + (cond + (pdf-cache-prefetch-minor-mode + (pdf-util-assert-pdf-buffer) + (add-hook 'pre-command-hook 'pdf-cache--prefetch-stop nil t) + ;; FIXME: Disable the time when the buffer is killed or it's + ;; major-mode changes. + (setq pdf-cache--prefetch-timer + (run-with-idle-timer (or pdf-cache-prefetch-delay 1) + t 'pdf-cache--prefetch-start (current-buffer)))) + (t + (remove-hook 'pre-command-hook 'pdf-cache--prefetch-stop t)))) + +(defun pdf-cache-prefetch-pages-function-default () + (let ((page (pdf-view-current-page))) + (pdf-util-remove-duplicates + (cl-remove-if-not + (lambda (page) + (and (>= page 1) + (<= page (pdf-cache-number-of-pages)))) + (append + ;; +1, -1, +2, -2, ... + (let ((sign 1) + (incr 1)) + (mapcar (lambda (_) + (setq page (+ page (* sign incr)) + sign (- sign) + incr (1+ incr)) + page) + (number-sequence 1 16))) + ;; First and last + (list 1 (pdf-cache-number-of-pages)) + ;; Links + (mapcar + (apply-partially 'alist-get 'page) + (cl-remove-if-not + (lambda (link) (eq (alist-get 'type link) 'goto-dest)) + (pdf-cache-pagelinks + (pdf-view-current-page))))))))) + +(defvar pdf-view-use-scaling) +(defun pdf-cache--prefetch-pages (window image-width) + (when (and (eq window (selected-window)) + (pdf-util-pdf-buffer-p)) + (let ((page (pop pdf-cache--prefetch-pages))) + (while (and page + (pdf-cache-lookup-image + page + image-width + (if (not pdf-view-use-scaling) + image-width + (* 2 image-width)))) + (setq page (pop pdf-cache--prefetch-pages))) + (pdf-util-debug + (when (null page) + (message "Prefetching done."))) + (when page + (let* ((buffer (current-buffer)) + (pdf-info-asynchronous + (lambda (status data) + (when (and (null status) + (eq window + (selected-window)) + (eq buffer (window-buffer))) + (with-current-buffer (window-buffer) + (when (derived-mode-p 'pdf-view-mode) + (pdf-cache-put-image + page image-width data) + (image-size (pdf-view-create-page page)) + (pdf-util-debug + (message "Prefetched page %s." page)) + ;; Avoid max-lisp-eval-depth + (run-with-timer + 0.001 nil 'pdf-cache--prefetch-pages window image-width))))))) + (condition-case err + (pdf-info-renderpage page image-width) + (error + (pdf-cache-prefetch-minor-mode -1) + (signal (car err) (cdr err))))))))) + +(defvar pdf-cache--prefetch-started-p nil + "Guard against multiple prefetch starts. + +Used solely in `pdf-cache--prefetch-start'.") + +(defun pdf-cache--prefetch-start (buffer) + "Start prefetching images in BUFFER." + (when (and pdf-cache-prefetch-minor-mode + (not pdf-cache--prefetch-started-p) + (pdf-util-pdf-buffer-p) + (not isearch-mode) + (null pdf-cache--prefetch-pages) + (eq (window-buffer) buffer) + (fboundp pdf-cache-prefetch-pages-function)) + (let* ((pdf-cache--prefetch-started-p t) + (pages (funcall pdf-cache-prefetch-pages-function))) + (setq pdf-cache--prefetch-pages + (butlast pages (max 0 (- (length pages) + pdf-cache-image-limit)))) + (pdf-cache--prefetch-pages + (selected-window) + (car (pdf-view-desired-image-size)))))) + +(defun pdf-cache--prefetch-stop () + "Stop prefetching images in current buffer." + (setq pdf-cache--prefetch-pages nil)) + +(defun pdf-cache--prefetch-cancel () + "Cancel prefetching images in current buffer." + (pdf-cache--prefetch-stop) + (when pdf-cache--prefetch-timer + (cancel-timer pdf-cache--prefetch-timer)) + (setq pdf-cache--prefetch-timer nil)) + +(provide 'pdf-cache) +;;; pdf-cache.el ends here diff --git a/elpa/pdf-tools-20211110.513/pdf-cache.elc b/elpa/pdf-tools-20211110.513/pdf-cache.elc Binary files differ. diff --git a/elpa/pdf-tools-20211110.513/pdf-dev.el b/elpa/pdf-tools-20211110.513/pdf-dev.el @@ -0,0 +1,85 @@ +;;; pdf-dev.el --- Mother's little development helper -*- lexical-binding: t; -*- + +;; Copyright (C) 2015 Andreas Politz + +;; Author: Andreas Politz <politza@hochschule-trier.de> +;; Keywords: + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: +;; +;; This file is only meant for developers. The entry point is +;; pdf-dev-minor-mode, which see. + +;;; Code: + +(defvar pdf-dev-root-directory + (file-name-directory + (directory-file-name + (file-name-directory load-file-name)))) + +(defun pdf-dev-reload () + "Reload lisp files from source." + (interactive) + (let ((default-directory (expand-file-name + "lisp" + pdf-dev-root-directory)) + loaded) + (dolist (file (directory-files default-directory nil "\\`pdf-\\w*\\.el\\'")) + (push file loaded) + (load-file file)) + (message "Loaded %s" (mapconcat 'identity loaded " ")))) + +(define-minor-mode pdf-dev-minor-mode + "Make developing pdf-tools easier. + +It does the following: + +Quits the server and sets `pdf-info-epdfinfo-program' to +../server/epdfinfo. + +Installs a `compilation-finish-functions' which will restart +epdfinfo after a successful recompilation. + +Sets up `load-path' and reloads all PDF Tools lisp files." + :group 'pdf-dev + (let ((lisp-dir (expand-file-name "lisp" pdf-dev-root-directory))) + (setq load-path (remove lisp-dir load-path)) + (cond + (pdf-dev-minor-mode + (add-hook 'compilation-finish-functions 'pdf-dev-compilation-finished) + (add-to-list 'load-path lisp-dir) + (setq pdf-info-epdfinfo-program + (expand-file-name + "epdfinfo" + (expand-file-name "server" pdf-dev-root-directory))) + (pdf-info-quit) + (pdf-dev-reload)) + (t + (remove-hook 'compilation-finish-functions 'pdf-dev-compilation-finished))))) + +(defun pdf-dev-compilation-finished (buffer status) + (with-current-buffer buffer + (when (and (equal status "finished\n") + (file-equal-p + (expand-file-name "server" pdf-dev-root-directory) + default-directory)) + (message "Restarting epdfinfo server") + (pdf-info-quit) + (let ((pdf-info-restart-process-p t)) + (pdf-info-process-assert-running))))) + +(provide 'pdf-dev) +;;; pdf-dev.el ends here diff --git a/elpa/pdf-tools-20211110.513/pdf-dev.elc b/elpa/pdf-tools-20211110.513/pdf-dev.elc Binary files differ. diff --git a/elpa/pdf-tools-20211110.513/pdf-history.el b/elpa/pdf-tools-20211110.513/pdf-history.el @@ -0,0 +1,170 @@ +;;; pdf-history.el --- A simple stack-based history in PDF buffers. -*- lexical-binding: t -*- + +;; Copyright (C) 2013, 2014 Andreas Politz + +;; Author: Andreas Politz <politza@fh-trier.de> +;; Keywords: files, multimedia + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: +;; + +(require 'pdf-view) +(require 'pdf-util) + +;;; Code: + +(defgroup pdf-history nil + "A simple stack-based history." + :group 'pdf-tools) + +(defvar-local pdf-history-stack nil + "The stack of history items.") + +(defvar-local pdf-history-index nil + "The current index into the `pdf-history-stack'.") + +(defvar pdf-history-minor-mode-map + (let ((kmap (make-sparse-keymap))) + (define-key kmap (kbd "B") 'pdf-history-backward) + (define-key kmap (kbd "N") 'pdf-history-forward) + kmap) + "Keymap used in `pdf-history-minor-mode'.") + +;;;###autoload +(define-minor-mode pdf-history-minor-mode + "Keep a history of previously visited pages. + +This is a simple stack-based history. Turning the page or +following a link pushes the left-behind page on the stack, which +may be navigated with the following keys. + +\\{pdf-history-minor-mode-map}" + :group 'pdf-history + (pdf-util-assert-pdf-buffer) + (pdf-history-clear) + (cond + (pdf-history-minor-mode + (pdf-history-push) + (add-hook 'pdf-view-after-change-page-hook + 'pdf-history-before-change-page-hook nil t)) + (t + (remove-hook 'pdf-view-after-change-page-hook + 'pdf-history-before-change-page-hook t)))) + +(defun pdf-history-before-change-page-hook () + "Push a history item, before leaving this page." + (when (and pdf-history-minor-mode + (not (bound-and-true-p pdf-isearch-active-mode)) + (pdf-view-current-page)) + (pdf-history-push))) + +(defun pdf-history-push () + "Push the current page on the stack. + +This function does nothing, if current stack item already +represents the current page." + (interactive) + (let ((item (pdf-history-create-item))) + (unless (and pdf-history-stack + (equal (nth pdf-history-index + pdf-history-stack) item)) + (setq pdf-history-stack + (last pdf-history-stack + (- (length pdf-history-stack) + pdf-history-index)) + pdf-history-index 0) + (push item pdf-history-stack)))) + +(defun pdf-history-clear () + "Remove all history items." + (interactive) + (setq pdf-history-stack nil + pdf-history-index 0) + (pdf-history-push)) + +(defun pdf-history-create-item () + "Create a history item representing the current page." + (list + (pdf-view-current-page))) + +(defun pdf-history-beginning-of-history-p () + "Return t, if at the beginning of the history." + (= pdf-history-index 0)) + +(defun pdf-history-end-of-history-p () + "Return t, if at the end of the history." + (= pdf-history-index + (1- (length pdf-history-stack)))) + +(defun pdf-history-backward (n) + "Go N-times backward in the history." + (interactive "p") + (cond + ((and (> n 0) + (pdf-history-end-of-history-p)) + (error "End of history")) + ((and (< n 0) + (pdf-history-beginning-of-history-p)) + (error "Beginning of history")) + ((/= n 0) + (let ((i (min (max 0 (+ pdf-history-index n)) + (1- (length pdf-history-stack))))) + (prog1 + (- (+ pdf-history-index n) i) + (pdf-history-goto i)))) + (t 0))) + +(defun pdf-history-forward (n) + "Go N-times forward in the history." + (interactive "p") + (pdf-history-backward (- n))) + +(defun pdf-history-goto (n) + "Go to item N in the history." + (interactive "p") + (when (null pdf-history-stack) + (error "The history is empty")) + (cond + ((>= n (length pdf-history-stack)) + (error "End of history")) + ((< n 0) + (error "Beginning of history")) + (t + (setq pdf-history-index n) + (pdf-view-goto-page + (car (nth n pdf-history-stack)))))) + +(defun pdf-history-debug () + "Visualize the history in the header-line." + (interactive) + (setq header-line-format + '(:eval + (let ((pages (mapcar 'car pdf-history-stack)) + (index pdf-history-index) + header) + (dotimes (i (length pages)) + (push (propertize + (format "%s" (nth i pages)) + 'face + (and (= i index) 'match)) + header)) + (concat + "(" (format "%d" index) ") " + (mapconcat 'identity (nreverse header) " | ")))))) + +(provide 'pdf-history) + +;;; pdf-history.el ends here diff --git a/elpa/pdf-tools-20211110.513/pdf-history.elc b/elpa/pdf-tools-20211110.513/pdf-history.elc Binary files differ. diff --git a/elpa/pdf-tools-20211110.513/pdf-info.el b/elpa/pdf-tools-20211110.513/pdf-info.el @@ -0,0 +1,1744 @@ +;;; pdf-info.el --- Extract info from pdf-files via a helper process. -*- lexical-binding: t -*- + +;; Copyright (C) 2013, 2014 Andreas Politz + +;; Author: Andreas Politz <politza@fh-trier.de> +;; Keywords: files, multimedia + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: +;; +;; This library represents the Lisp side of the epdfinfo server. This +;; program works on a command/response basis, but there should be no +;; need to understand the protocol, since every command has a +;; corresponding Lisp-function (see below under `High level +;; interface'). +;; +;; Most of these functions receive a file-or-buffer argument, which +;; may be what it says and defaults to the current buffer. Also, most +;; functions return some sort of alist, with, in most cases, +;; straight-forward key-value-pairs. Though some may be only +;; understandable in the context of Adobe's PDF spec \(Adobe +;; PDF32000\) or the poppler documentation (e.g. annotation flags). +;; +;; If the poppler library is fairly recent (>= 0.19.4, older versions +;; have a bug, which may corrupt the document), annotations maybe +;; modified to a certain degree, deleted and text-annotations created. +;; The state of these modifications is held in the server. In order +;; to realize, annotations retrieved or created are referenced by a +;; unique symbol. Saving these changes creates a new file, the +;; original document is never touched. + +;;; Todo: +;; +;; + Close documents at some time (e.g. when the buffer is killed) +;; + +;;; Code: + +(require 'tq) +(require 'cl-lib) + + + +;; * ================================================================== * +;; * Customizations +;; * ================================================================== * + +(defgroup pdf-info nil + "Extract infos from pdf-files via a helper process." + :group 'pdf-tools) + +(defcustom pdf-info-epdfinfo-program + (let ((executable (if (eq system-type 'windows-nt) + "epdfinfo.exe" "epdfinfo")) + (default-directory + (or (and load-file-name + (file-name-directory load-file-name)) + default-directory))) + (cl-labels ((try-directory (directory) + (and (file-directory-p directory) + (file-executable-p (expand-file-name executable directory)) + (expand-file-name executable directory)))) + (or (executable-find executable) + ;; This works if epdfinfo is in the same place as emacs and + ;; the editor was started with an absolute path, i.e. it is + ;; meant for Windows/Msys2. + (and (stringp (car-safe command-line-args)) + (file-name-directory (car command-line-args)) + (try-directory + (file-name-directory (car command-line-args)))) + ;; If we are running directly from the git repo. + (try-directory (expand-file-name "../server")) + ;; Fall back to epdfinfo in the directory of this file. + (expand-file-name executable)))) + "Filename of the epdfinfo executable." + :group 'pdf-info + :type 'file) + +(defcustom pdf-info-epdfinfo-error-filename nil + "Filename for error output of the epdfinfo executable. + +If nil, discard any error messages. Useful for debugging." + :group 'pdf-info + :type `(choice (const :tag "None" nil) + ,@(when (file-directory-p "/tmp/") + '((const "/tmp/epdfinfo.log"))) + (file))) + +(defcustom pdf-info-log nil + "Whether to log the communication with the server. + +If this is non-nil, all communication with the epdfinfo program +will be logged to the buffer \"*pdf-info-log*\"." + :group 'pdf-info + :type 'boolean) + +(defcustom pdf-info-log-entry-max 512 + "Maximum number of characters in a single log entry. + +This variable has no effect if `pdf-info-log' is nil." + :group 'pdf-info + :type 'integer) + +(defcustom pdf-info-restart-process-p 'ask + "What to do when the epdfinfo server died. + +This should be one of +nil -- do nothing, +t -- automatically restart it or +ask -- ask whether to restart or not. + +If it is `ask', the server quits and you answer no, this variable +is set to nil." + :group 'pdf-info + :type '(choice (const :tag "Do nothing" nil) + (const :tag "Restart silently" t) + (const :tag "Always ask" ask))) + +(defcustom pdf-info-close-document-hook nil + "A hook ran after a document was closed in the server. + +The hook is run in the documents buffer, if it exists. Otherwise +in a `with-temp-buffer' form." + :group 'pdf-info + :type 'hook) + + + +;; * ================================================================== * +;; * Variables +;; * ================================================================== * + +(defvar pdf-info-asynchronous nil + "If non-nil process queries asynchronously. + +More specifically the value should be a function of at 2 +arguments \(fn STATUS RESPONSE\), where STATUS is either nil, for +a successful query, or the symbol error. RESPONSE is either the +command's response or the error message. This does not work +recursive, i.e. if function wants to make another asynchronous +query it has to rebind this variable. + +Alternatively it may be a list \(FN . ARGS\), in which case FN +will be invoked like \(apply FN STATUS RESPONSE ARGS\). + +Also, all pdf-info functions normally returning a response return +nil. + +This variable should only be let-bound.") + +(defconst pdf-info-pdf-date-regexp + ;; Adobe PDF32000.book, 7.9.4 Dates + (eval-when-compile + (concat + ;; allow for preceding garbage + ;;"\\`" + "[dD]:" + "\\([0-9]\\{4\\}\\)" ;year + "\\(?:" + "\\([0-9]\\{2\\}\\)" ;month + "\\(?:" + "\\([0-9]\\{2\\}\\)" ;day + "\\(?:" + "\\([0-9]\\{2\\}\\)" ;hour + "\\(?:" + "\\([0-9]\\{2\\}\\)" ;minutes + "\\(?:" + "\\([0-9]\\{2\\}\\)" ;seconds + "\\)?\\)?\\)?\\)?\\)?" + "\\(?:" + "\\([+-Zz]\\)" ;UT delta char + "\\(?:" + "\\([0-9]\\{2\\}\\)" ;UT delta hours + "\\(?:" + "'" + "\\([0-9]\\{2\\}\\)" ;UT delta minutes + "\\)?\\)?\\)?" + ;; "\\'" + ;; allow for trailing garbage + ))) + +(defvar pdf-info--queue t + "Internally used transmission-queue for the server. + +This variable is initially `t', telling the code starting the +server, that it never ran.") + + +;; * ================================================================== * +;; * Process handling +;; * ================================================================== * + +(defconst pdf-info-empty-page-data + (eval-when-compile + (concat + "%PDF-1.0\n1 0 obj<</Type/Catalog/Pages 2 0 R>>endobj 2 0" + " obj<</Type/Pages/Kids[3 0 R]/Count 1>>endobj 3 0 obj<</" + "Type/Page/MediaBox[0 0 3 3]>>endobj\nxref\n0 4\n00000000" + "0065535 f\n0000000010 00000 n\n0000000053 00000 n\n00000" + "00102 00000 n\ntrailer<</Size 4/Root 1 0 R>>\nstartxref\n149\n%EOF")) + "PDF data of an empty page.") + +(defun pdf-info-process () + "Return the process object or nil." + (and pdf-info--queue + (not (eq t pdf-info--queue)) + (tq-process pdf-info--queue))) + +(defun pdf-info-check-epdfinfo (&optional interactive-p) + "Check if the server should be working properly. + +Signal an error if some problem was found. Message a +confirmation, if INTERACTIVE-P is non-nil and no problems were +found. + +Returns nil." + (interactive "p") + (let ((executable pdf-info-epdfinfo-program)) + (unless (stringp executable) + (error "pdf-info-epdfinfo-program is unset or not a string")) + (unless (file-executable-p executable) + (error "pdf-info-epdfinfo-program is not executable")) + (when pdf-info-epdfinfo-error-filename + (unless (and (stringp pdf-info-epdfinfo-error-filename) + (file-writable-p pdf-info-epdfinfo-error-filename)) + (error "pdf-info-epdfinfo-error-filename should contain writable filename"))) + (let* ((default-directory (expand-file-name "~/")) + (cmdfile (make-temp-file "commands")) + (pdffile (make-temp-file "empty.pdf")) + (tempdir (make-temp-file "tmpdir" t)) + (process-environment (cons (concat "TMPDIR=" tempdir) + process-environment))) + (unwind-protect + (with-temp-buffer + (with-temp-file pdffile + (set-buffer-multibyte nil) + (insert pdf-info-empty-page-data)) + (with-temp-file cmdfile + (insert (format "renderpage:%s:1:100\nquit\n" + (pdf-info-query--escape pdffile)))) + (unless (= 0 (apply #'call-process + executable cmdfile (current-buffer) + nil (when pdf-info-epdfinfo-error-filename + (list pdf-info-epdfinfo-error-filename)))) + (error "Error running `%s': %s" + pdf-info-epdfinfo-program + (buffer-string)))) + (when (file-exists-p cmdfile) + (delete-file cmdfile)) + (when (file-exists-p pdffile) + (delete-file pdffile)) + (when (file-exists-p tempdir) + (delete-directory tempdir t))))) + (when interactive-p + (message "The epdfinfo program appears to be working.")) + nil) + +(defun pdf-info-process-assert-running (&optional force) + "Assert a running process. + +If it never ran, i.e. `pdf-info-process' is t, start it +unconditionally. + +Otherwise, if FORCE is non-nil start it, if it is not running. +Else restart it with respect to the variable +`pdf-info-restart-process-p', which see. + +If getting the process to run fails, this function throws an +error." + (interactive "P") + (unless (and (processp (pdf-info-process)) + (eq (process-status (pdf-info-process)) + 'run)) + (when (pdf-info-process) + (tq-close pdf-info--queue) + (setq pdf-info--queue nil)) + (unless (or force + (eq pdf-info--queue t) + (and (eq pdf-info-restart-process-p 'ask) + (not noninteractive) + (y-or-n-p "The epdfinfo server quit, restart it ? ")) + (and pdf-info-restart-process-p + (not (eq pdf-info-restart-process-p 'ask)))) + + (when (eq pdf-info-restart-process-p 'ask) + (setq pdf-info-restart-process-p nil)) + (error "The epdfinfo server quit")) + (pdf-info-check-epdfinfo) + (let* ((process-connection-type) ;Avoid 4096 Byte bug #12440. + (default-directory "~") + (proc (apply #'start-process + "epdfinfo" " *epdfinfo*" pdf-info-epdfinfo-program + (when pdf-info-epdfinfo-error-filename + (list pdf-info-epdfinfo-error-filename))))) + (with-current-buffer " *epdfinfo*" + (erase-buffer)) + (set-process-query-on-exit-flag proc nil) + (set-process-coding-system proc 'utf-8-unix 'utf-8-unix) + (setq pdf-info--queue (tq-create proc)))) + pdf-info--queue) + +(defadvice tq-process-buffer (around bugfix activate) + "Fix a bug in trunk where the wrong callback gets called." + ;; FIXME: Make me iterative. + (let ((tq (ad-get-arg 0))) + (if (not (equal (car (process-command (tq-process tq))) + pdf-info-epdfinfo-program)) + ad-do-it + (let ((buffer (tq-buffer tq)) + done) + (when (buffer-live-p buffer) + (set-buffer buffer) + (while (and (not done) + (> (buffer-size) 0)) + (setq done t) + (if (tq-queue-empty tq) + (let ((buf (generate-new-buffer "*spurious*"))) + (copy-to-buffer buf (point-min) (point-max)) + (delete-region (point-min) (point)) + (pop-to-buffer buf nil) + (error "Spurious communication from process %s, see buffer %s" + (process-name (tq-process tq)) + (buffer-name buf))) + (goto-char (point-min)) + (when (re-search-forward (tq-queue-head-regexp tq) nil t) + (setq done nil) + (let ((answer (buffer-substring (point-min) (point))) + (fn (tq-queue-head-fn tq)) + (closure (tq-queue-head-closure tq))) + (delete-region (point-min) (point)) + (tq-queue-pop tq) + (condition-case-unless-debug err + (funcall fn closure answer) + (error + (message "Error while processing tq callback: %s" + (error-message-string err))))))))))))) + + +;; * ================================================================== * +;; * Sending and receiving +;; * ================================================================== * + +(defun pdf-info-query (cmd &rest args) + "Query the server using CMD and ARGS." + (pdf-info-process-assert-running) + (unless (symbolp cmd) + (setq cmd (intern cmd))) + (let* ((query (concat (mapconcat 'pdf-info-query--escape + (cons cmd args) ":") "\n")) + (callback + (lambda (closure response) + (cl-destructuring-bind (status &rest result) + (pdf-info-query--parse-response cmd response) + (pdf-info-query--log response) + (let* (pdf-info-asynchronous) + (if (functionp closure) + (funcall closure status result) + (apply (car closure) status result (cdr closure))))))) + response status done + (closure (or pdf-info-asynchronous + (lambda (s r) + (setq status s response r done t))))) + (pdf-info-query--log query t) + (tq-enqueue + pdf-info--queue query "^\\.\n" closure callback) + (unless pdf-info-asynchronous + (while (and (not done) + (eq (process-status (pdf-info-process)) + 'run)) + (accept-process-output (pdf-info-process) 0.01)) + (when (and (not done) + (not (eq (process-status (pdf-info-process)) + 'run)) + (not (eq cmd 'quit))) + (error "The epdfinfo server quit unexpectedly.")) + (cond + ((null status) response) + ((eq status 'error) + (error "epdfinfo: %s" response)) + ((eq status 'interrupted) + (error "epdfinfo: Command was interrupted")) + (t + (error "internal error: invalid response status")))))) + +(defun pdf-info-interrupt () + "FIXME: This command does currently nothing." + (when (and (processp (pdf-info-process)) + (eq (process-status (pdf-info-process)) + 'run)) + (signal-process (pdf-info-process) 'SIGUSR1))) + +(defun pdf-info-query--escape (arg) + "Escape ARG for transmission to the server." + (if (null arg) + (string) + (with-current-buffer (get-buffer-create " *pdf-info-query--escape*") + (erase-buffer) + (insert (format "%s" arg)) + (goto-char 1) + (while (not (eobp)) + (cond + ((memq (char-after) '(?\\ ?:)) + (insert ?\\)) + ((eq (char-after) ?\n) + (delete-char 1) + (insert ?\\ ?n) + (backward-char))) + (forward-char)) + (buffer-substring-no-properties 1 (point-max))))) + +(defmacro pdf-info-query--read-record () + "Read a single record of the response in current buffer." + `(let (records done (beg (point))) + (while (not done) + (cl-case (char-after) + (?\\ + (delete-char 1) + (if (not (eq (char-after) ?n)) + (forward-char) + (delete-char 1) + (insert ?\n))) + ((?: ?\n) + (push (buffer-substring-no-properties + beg (point)) records) + (forward-char) + (setq beg (point) + done (bolp))) + (t (forward-char)))) + (nreverse records))) + +(defun pdf-info-query--parse-response (cmd response) + "Parse one epdfinfo RESPONSE to CMD. + +Returns a cons \(STATUS . RESULT\), where STATUS is one of nil +for a regular response, error for an error \(RESULT contains the +error message\) or interrupted, i.e. the command was +interrupted." + (with-current-buffer + (get-buffer-create " *pdf-info-query--parse-response*") + (erase-buffer) + (insert response) + (goto-char 1) + (cond + ((looking-at "ERR\n") + (forward-line) + (cons 'error (buffer-substring-no-properties + (point) + (progn + (re-search-forward "^\\.\n") + (1- (match-beginning 0)))))) + ((looking-at "OK\n") + (let (result) + (forward-line) + (while (not (and (= (char-after) ?.) + (= (char-after (1+ (point))) ?\n))) + (push (pdf-info-query--read-record) result)) + (cons nil (pdf-info-query--transform-response + cmd (nreverse result))))) + ((looking-at "INT\n") + (cons 'interrupted nil)) + (t + (cons 'error "Invalid server response"))))) + +(defun pdf-info-query--transform-response (cmd response) + "Transform a RESPONSE to CMD into a Lisp form." + (cl-case cmd + (open nil) + (close (equal "1" (caar response))) + (number-of-pages (string-to-number (caar response))) + (charlayout + (mapcar (lambda (elt) + (cl-assert (= 1 (length (cadr elt))) t) + `(,(aref (cadr elt) 0) + ,(mapcar 'string-to-number + (split-string (car elt) " " t)))) + response)) + (regexp-flags + (mapcar (lambda (elt) + (cons (intern (car elt)) + (string-to-number (cadr elt)))) + response)) + ((search-string search-regexp) + (mapcar + (lambda (r) + `((page . ,(string-to-number (nth 0 r))) + (text . ,(let (case-fold-search) + (pdf-util-highlight-regexp-in-string + (regexp-quote (nth 1 r)) (nth 2 r)))) + (edges . ,(mapcar (lambda (m) + (mapcar 'string-to-number + (split-string m " " t))) + (cddr (cdr r)))))) + response)) + (outline + (mapcar (lambda (r) + `((depth . ,(string-to-number (pop r))) + ,@(pdf-info-query--transform-action r))) + response)) + (pagelinks + (mapcar (lambda (r) + `((edges . + ,(mapcar 'string-to-number ;area + (split-string (pop r) " " t))) + ,@(pdf-info-query--transform-action r))) + response)) + (metadata + (let ((md (car response))) + (if (= 1 (length md)) + (list (cons 'title (car md))) + (list + (cons 'title (pop md)) + (cons 'author (pop md)) + (cons 'subject (pop md)) + (cons 'keywords-raw (car md)) + (cons 'keywords (split-string (pop md) "[\t\n ]*,[\t\n ]*" t)) + (cons 'creator (pop md)) + (cons 'producer (pop md)) + (cons 'format (pop md)) + (cons 'created (pop md)) + (cons 'modified (pop md)))))) + (gettext + (or (caar response) "")) + (getselection + (mapcar (lambda (line) + (mapcar 'string-to-number + (split-string (car line) " " t))) + response)) + (features (mapcar 'intern (car response))) + (pagesize + (setq response (car response)) + (cons (round (string-to-number (car response))) + (round (string-to-number (cadr response))))) + ((getannot editannot addannot) + (pdf-info-query--transform-annotation (car response))) + (getannots + (mapcar 'pdf-info-query--transform-annotation response)) + (getattachments + (mapcar 'pdf-info-query--transform-attachment response)) + ((getattachment-from-annot) + (pdf-info-query--transform-attachment (car response))) + (boundingbox + (mapcar 'string-to-number (car response))) + (synctex-forward-search + (let ((list (mapcar 'string-to-number (car response)))) + `((page . ,(car list)) + (edges . ,(cdr list))))) + (synctex-backward-search + `((filename . ,(caar response)) + (line . ,(string-to-number (cadr (car response)))) + (column . ,(string-to-number (cadr (cdar response)))))) + (delannot nil) + ((save) (caar response)) + ((renderpage renderpage-text-regions renderpage-highlight) + (pdf-util-munch-file (caar response))) + ((setoptions getoptions) + (let (options) + (dolist (key-value response) + (let ((key (intern (car key-value))) + (value (cadr key-value))) + (cl-case key + ((:render/printed :render/usecolors) + (setq value (equal value "1")))) + (push value options) + (push key options))) + options)) + (pagelabels (mapcar 'car response)) + (ping (caar response)) + (t response))) + + +(defun pdf-info-query--transform-action (action) + "Transform ACTION response into a Lisp form." + (let ((type (intern (pop action)))) + `((type . ,type) + (title . ,(pop action)) + ,@(cl-case type + (goto-dest + `((page . ,(string-to-number (pop action))) + (top . ,(and (> (length (car action)) 0) + (string-to-number (pop action)))))) + (goto-remote + `((filename . ,(pop action)) + (page . ,(string-to-number (pop action))) + (top . ,(and (> (length (car action)) 0) + (string-to-number (pop action)))))) + (t `((uri . ,(pop action)))))))) + +(defun pdf-info-query--transform-annotation (a) + (cl-labels ((not-empty (s) + (if (not (equal s "")) s))) + (let (a1 a2 a3) + (cl-destructuring-bind (page edges type id flags color contents modified &rest rest) + a + (setq a1 `((page . ,(string-to-number page)) + (edges . ,(mapcar 'string-to-number + (split-string edges " " t))) + (type . ,(intern type)) + (id . ,(intern id)) + (flags . ,(string-to-number flags)) + (color . ,(not-empty color)) + (contents . ,contents) + (modified . ,(pdf-info-parse-pdf-date modified)))) + (when rest + (cl-destructuring-bind (label subject opacity popup-edges popup-is-open created + &rest rest) + rest + (setq a2 + `((label . ,(not-empty label)) + (subject . ,(not-empty subject)) + (opacity . ,(let ((o (not-empty opacity))) + (and o (string-to-number o)))) + (popup-edges . ,(let ((p (not-empty popup-edges))) + (when p + (mapcar 'string-to-number + (split-string p " " t))))) + (popup-is-open . ,(equal popup-is-open "1")) + (created . ,(pdf-info-parse-pdf-date (not-empty created))))) + (cond + ((eq (cdr (assoc 'type a1)) 'text) + (cl-destructuring-bind (icon state is-open) + rest + (setq a3 + `((icon . ,(not-empty icon)) + (state . ,(not-empty state)) + (is-open . ,(equal is-open "1")))))) + ((memq (cdr (assoc 'type a1)) + '(squiggly highlight underline strike-out)) + (setq a3 `((markup-edges + . ,(mapcar (lambda (r) + (mapcar 'string-to-number + (split-string r " " t))) + rest))))))))) + (append a1 a2 a3)))) + +(defun pdf-info-query--transform-attachment (a) + (cl-labels ((not-empty (s) + (if (not (equal s "")) s))) + (cl-destructuring-bind (id filename description size modified + created checksum file) + a + `((id . ,(intern id)) + (filename . ,(not-empty filename)) + (description . ,(not-empty description)) + (size . ,(let ((n (string-to-number size))) + (and (>= n 0) n))) + (modified . ,(not-empty modified)) + (created . ,(not-empty created)) + (checksum . ,(not-empty checksum)) + (file . ,(not-empty file)))))) + +(defun pdf-info-query--log (string &optional query-p) + "Log STRING as query/response, depending on QUERY-P. + +This is a no-op, if `pdf-info-log' is nil." + (when pdf-info-log + (with-current-buffer (get-buffer-create "*pdf-info-log*") + (buffer-disable-undo) + (let ((pos (point-max)) + (window (get-buffer-window))) + (save-excursion + (goto-char (point-max)) + (unless (bolp) + (insert ?\n)) + (insert + (propertize + (format-time-string "%H:%M:%S ") + 'face + (if query-p + 'font-lock-keyword-face + 'font-lock-function-name-face)) + (if (and (numberp pdf-info-log-entry-max) + (> (length string) + pdf-info-log-entry-max)) + (concat (substring string 0 pdf-info-log-entry-max) + "...[truncated]\n") + string))) + (when (and (window-live-p window) + (= pos (window-point window))) + (set-window-point window (point-max))))))) + + + +;; * ================================================================== * +;; * Utility functions +;; * ================================================================== * + +(defvar doc-view-buffer-file-name) +(defvar doc-view--buffer-file-name) + +(defun pdf-info--normalize-file-or-buffer (file-or-buffer) + "Return the PDF file corresponding to FILE-OR-BUFFER. + +FILE-OR-BUFFER may be nil, a PDF buffer, the name of a PDF buffer +or a PDF file." + (unless file-or-buffer + (setq file-or-buffer + (current-buffer))) + (when (bufferp file-or-buffer) + (unless (buffer-live-p file-or-buffer) + (error "Buffer is not live :%s" file-or-buffer)) + (with-current-buffer file-or-buffer + (unless (setq file-or-buffer + (cl-case major-mode + (doc-view-mode + (cond ((boundp 'doc-view-buffer-file-name) + doc-view-buffer-file-name) + ((boundp 'doc-view--buffer-file-name) + doc-view--buffer-file-name))) + (pdf-view-mode (pdf-view-buffer-file-name)) + (t (buffer-file-name)))) + (error "Buffer is not associated with any file :%s" (buffer-name))))) + (unless (stringp file-or-buffer) + (signal 'wrong-type-argument + (list 'stringp 'bufferp 'null file-or-buffer))) + ;; is file + (when (file-remote-p file-or-buffer) + (error "Processing remote files not supported :%s" + file-or-buffer)) + ;; (unless (file-readable-p file-or-buffer) + ;; (error "File not readable :%s" file-or-buffer)) + (expand-file-name file-or-buffer)) + +(defun pdf-info-valid-page-spec-p (pages) + "The type predicate for a valid page-spec." + (not (not (ignore-errors (pdf-info-normalize-page-range pages))))) + +(defun pdf-info-normalize-page-range (pages) + "Normalize PAGES for sending to the server. + +PAGES may be a single page number, a cons \(FIRST . LAST\), or +nil, which stands for all pages. + +The result is a cons \(FIRST . LAST\), where LAST may be 0 +representing the final page." + (cond + ((natnump pages) + (cons pages pages)) + ((null pages) + (cons 1 0)) + ((and (natnump (car pages)) + (natnump (cdr pages))) + pages) + (t + (signal 'wrong-type-argument + (list 'pdf-info-valid-page-spec-p pages))))) + +(defun pdf-info-parse-pdf-date (date) + (when (and date + (string-match pdf-info-pdf-date-regexp date)) + (let ((year (match-string 1 date)) + (month (match-string 2 date)) + (day (match-string 3 date)) + (hour (match-string 4 date)) + (min (match-string 5 date)) + (sec (match-string 6 date)) + (ut-char (match-string 7 date)) + (ut-hour (match-string 8 date)) + (ut-min (match-string 9 date)) + (tz 0)) + (when (or (equal ut-char "+") + (equal ut-char "-")) + (when ut-hour + (setq tz (* 3600 (string-to-number ut-hour)))) + (when ut-min + (setq tz (+ tz (* 60 (string-to-number ut-min))))) + (when (equal ut-char "-") + (setq tz (- tz)))) + (encode-time + (if sec (string-to-number sec) 0) + (if min (string-to-number min) 0) + (if hour (string-to-number hour) 0) + (if day (string-to-number day) 1) + (if month (string-to-number month) 1) + (string-to-number year) + tz)))) + +(defmacro pdf-info-compose-queries (let-forms &rest body) + "Let-bind each VAR to QUERIES results and evaluate BODY. + +All queries in each QUERIES form are run by the server in the +order they appear and the results collected in a list, which is +bound to VAR. Then BODY is evaluated and its value becomes the +final result of all queries, unless at least one of them provoked +an error. In this case BODY is ignored and the error is the +result. + +This macro handles synchronous and asynchronous calls, +i.e. `pdf-info-asynchronous' is non-nil, transparently. + +\(FN \(\(VAR QUERIES\)...\) BODY\)" + (declare (indent 1) + (debug ((&rest &or + (symbolp &optional form) + symbolp) + body))) + (unless (cl-every (lambda (form) + (when (symbolp form) + (setq form (list form))) + (and (consp form) + (symbolp (car form)) + (listp (cdr form)))) + let-forms) + (error "Invalid let-form: %s" let-forms)) + + (setq let-forms (mapcar (lambda (form) + (if (symbolp form) + (list form) + form)) + let-forms)) + (let* ((status (make-symbol "status")) + (response (make-symbol "response")) + (first-error (make-symbol "first-error")) + (done (make-symbol "done")) + (callback (make-symbol "callback")) + (results (make-symbol "results")) + (push-fn (make-symbol "push-fn")) + (terminal-fn (make-symbol "terminal-fn")) + (buffer (make-symbol "buffer"))) + `(let* (,status + ,response ,first-error ,done + (,buffer (current-buffer)) + (,callback pdf-info-asynchronous) + ;; Ensure a new alist on every invocation. + (,results (mapcar 'copy-sequence + ',(cl-mapcar (lambda (form) + (list (car form))) + let-forms))) + (,push-fn (lambda (status result var) + ;; Store result in alist RESULTS under key + ;; VAR. + (if status + (unless ,first-error + (setq ,first-error result)) + (let ((elt (assq var ,results))) + (setcdr elt (append (cdr elt) + (list result))))))) + (,terminal-fn + (lambda (&rest _) + ;; Let-bind responses corresponding to their variables, + ;; i.e. keys in alist RESULTS. + (let (,@(mapcar (lambda (var) + (list var (list 'cdr (list 'assq (list 'quote var) + results)))) + (mapcar 'car let-forms))) + (setq ,status (not (not ,first-error)) + ,response (or ,first-error + (with-current-buffer ,buffer + ,@body)) + ,done t) + ;; Maybe invoke the CALLBACK (which was bound to + ;; pdf-info-asynchronous). + (when ,callback + (if (functionp ,callback) + (funcall ,callback ,status ,response) + (apply (car ,callback) + ,status ,response (cdr ,callback)))))))) + ;; Wrap each query in an asynchronous call, with its VAR as + ;; callback argument, so the PUSH-FN can put it in the alist + ;; RESULTS. + ,@(mapcar (lambda (form) + (list 'let (list + (list 'pdf-info-asynchronous + (list 'list push-fn (list 'quote (car form))))) + (cadr form))) + let-forms) + ;; Request a no-op, just so we know that we are finished. + (let ((pdf-info-asynchronous ,terminal-fn)) + (pdf-info-ping)) + ;; CALLBACK is the original value of pdf-info-asynchronous. If + ;; nil, this is a synchronous query. + (unless ,callback + (while (and (not ,done) + (eq (process-status (pdf-info-process)) + 'run)) + (accept-process-output (pdf-info-process) 0.01)) + (when (and (not ,done) + (not (eq (process-status (pdf-info-process)) + 'run))) + (error "The epdfinfo server quit unexpectedly.")) + (when ,status + (error "epdfinfo: %s" ,response)) + ,response)))) + + +;; * ================================================================== * +;; * Buffer local server instances +;; * ================================================================== * + +(put 'pdf-info--queue 'permanent-local t) + +(defun pdf-info-make-local-server (&optional buffer force-restart-p) + "Create a server instance local to BUFFER. + +Does nothing if BUFFER already has a local instance. Unless +FORCE-RESTART-P is non-nil, then quit a potential process and +restart it." + (unless buffer + (setq buffer (current-buffer))) + (with-current-buffer buffer + (unless (and + (not force-restart-p) + (local-variable-p 'pdf-info--queue) + (processp (pdf-info-process)) + (eq (process-status (pdf-info-process)) + 'run)) + (when (and (local-variable-p 'pdf-info--queue) + (processp (pdf-info-process))) + (tq-close pdf-info--queue)) + (set (make-local-variable 'pdf-info--queue) nil) + (pdf-info-process-assert-running t) + (add-hook 'kill-buffer-hook 'pdf-info-kill-local-server nil t) + pdf-info--queue))) + +(defun pdf-info-kill-local-server (&optional buffer) + "Kill the local server in BUFFER. + +A No-op, if BUFFER has not running server instance." + (save-current-buffer + (when buffer + (set-buffer buffer)) + (when (local-variable-p 'pdf-info--queue) + (pdf-info-kill) + (kill-local-variable 'pdf-info--queue) + t))) + +(defun pdf-info-local-server-p (&optional buffer) + "Return non-nil, if BUFFER has a running server instance." + (unless buffer + (setq buffer (current-buffer))) + (setq buffer (get-buffer buffer)) + (and (buffer-live-p buffer) + (local-variable-p 'pdf-info--queue buffer))) + +(defun pdf-info-local-batch-query (producer-fn + consumer-fn + sentinel-fn + args) + "Process a set of queries asynchronously in a local instance." + (unless (pdf-info-local-server-p) + (error "Create a local server first")) + (let* ((buffer (current-buffer)) + (producer-symbol (make-symbol "producer")) + (consumer-symbol (make-symbol "consumer")) + (producer + (lambda (args) + (if (null args) + (funcall sentinel-fn 'finished buffer) + (let ((pdf-info-asynchronous + (apply-partially + (symbol-function consumer-symbol) + args))) + (cond + ((pdf-info-local-server-p buffer) + (with-current-buffer buffer + (apply producer-fn (car args)))) + (t + (funcall sentinel-fn 'error buffer))))))) + (consumer (lambda (args status result) + (if (not (pdf-info-local-server-p buffer)) + (funcall sentinel-fn 'error buffer) + (with-current-buffer buffer + (apply consumer-fn status result (car args))) + (funcall (symbol-function producer-symbol) + (cdr args)))))) + (fset producer-symbol producer) + (fset consumer-symbol consumer) + (funcall producer args))) + + + +;; * ================================================================== * +;; * High level interface +;; * ================================================================== * + +(defvar pdf-info-features nil) + +(defun pdf-info-features () + "Return a list of symbols describing compile-time features." + (or pdf-info-features + (setq pdf-info-features + (let (pdf-info-asynchronous) + (pdf-info-query 'features))))) + +(defun pdf-info-writable-annotations-p () + (not (null (memq 'writable-annotations (pdf-info-features))))) + +(defun pdf-info-markup-annotations-p () + (not (null (memq 'markup-annotations (pdf-info-features))))) + +(defmacro pdf-info-assert-writable-annotations () + `(unless (memq 'writable-annotations (pdf-info-features)) + (error "Writing annotations is not supported by this version of epdfinfo"))) + +(defmacro pdf-info-assert-markup-annotations () + `(unless (memq 'markup-annotations (pdf-info-features)) + (error "Creating markup annotations is not supported by this version of epdfinfo"))) + +(defun pdf-info-creatable-annotation-types () + (let ((features (pdf-info-features))) + (cond + ((not (memq 'writable-annotations features)) nil) + ((memq 'markup-annotations features) + (list 'text 'squiggly 'underline 'strike-out 'highlight)) + (t (list 'text))))) + +(defun pdf-info-open (&optional file-or-buffer password) + "Open the document FILE-OR-BUFFER using PASSWORD. + +Generally, documents are opened and closed automatically on +demand, so this function is rarely needed, unless a PASSWORD is +set on the document. + +Manually opened documents are never closed automatically." + + (pdf-info-query + 'open (pdf-info--normalize-file-or-buffer file-or-buffer) + password)) + +(defun pdf-info-close (&optional file-or-buffer) + "Close the document FILE-OR-BUFFER. + +Returns t, if the document was actually open, otherwise nil. +This command is rarely needed, see also `pdf-info-open'." + (let* ((pdf (pdf-info--normalize-file-or-buffer file-or-buffer)) + (buffer (find-buffer-visiting pdf))) + (prog1 + (pdf-info-query 'close pdf) + (if (buffer-live-p buffer) + (with-current-buffer buffer + (run-hooks 'pdf-info-close-document-hook)) + (with-temp-buffer + (run-hooks 'pdf-info-close-document-hook)))))) + +(defun pdf-info-encrypted-p (&optional file-or-buffer) + "Return non-nil if FILE-OR-BUFFER requires a password. + +Note: This function returns nil, if the document is encrypted, +but was already opened (presumably using a password)." + + (condition-case err + (pdf-info-open + (pdf-info--normalize-file-or-buffer file-or-buffer)) + (error (or (string-match-p + ":Document is encrypted\\'" (cadr err)) + (signal (car err) (cdr err)))))) + +(defun pdf-info-metadata (&optional file-or-buffer) + "Extract the metadata from the document FILE-OR-BUFFER. + +This returns an alist containing some information about the +document." + (pdf-info-query + 'metadata + (pdf-info--normalize-file-or-buffer file-or-buffer))) + +(defun pdf-info-search-string (string &optional pages file-or-buffer) + "Search for STRING in PAGES of document FILE-OR-BUFFER. + +See `pdf-info-normalize-page-range' for valid PAGES formats. + +This function returns a list of matches. Each item is an alist +containing keys PAGE, TEXT and EDGES, where PAGE and TEXT are the +matched page resp. line. EDGES is a list containing a single +edges element \(LEFT TOP RIGHT BOTTOM\). This is for consistency +with `pdf-info-search-regexp', which may return matches with +multiple edges. + +The TEXT contains `match' face properties on the matched parts. + +Search is case-insensitive, unless `case-fold-search' is nil and +searching case-sensitive is supported by the server." + + (let ((pages (pdf-info-normalize-page-range pages))) + (pdf-info-query + 'search-string + (pdf-info--normalize-file-or-buffer file-or-buffer) + (car pages) + (cdr pages) + string + (if case-fold-search 1 0)))) + +(defvar pdf-info-regexp-compile-flags nil + "PCRE compile flags. + +Don't use this, but the equally named function.") + +(defvar pdf-info-regexp-match-flags nil + "PCRE match flags. + +Don't use this, but the equally named function.") + +(defun pdf-info-regexp-compile-flags () + (or pdf-info-regexp-compile-flags + (let* (pdf-info-asynchronous + (flags (pdf-info-query 'regexp-flags)) + (match (cl-remove-if-not + (lambda (flag) + (string-match-p + "\\`match-" (symbol-name (car flag)))) + flags)) + (compile (cl-set-difference flags match))) + (setq pdf-info-regexp-compile-flags compile + pdf-info-regexp-match-flags match) + pdf-info-regexp-compile-flags))) + +(defun pdf-info-regexp-match-flags () + (or pdf-info-regexp-match-flags + (progn + (pdf-info-regexp-compile-flags) + pdf-info-regexp-match-flags))) + +(defvar pdf-info-regexp-flags '(multiline) + "Compile- and match-flags for the PCRE engine. + +This is a list of symbols denoting compile- and match-flags when +searching for regular expressions. + +You should not change this directly, but rather `let'-bind it +around a call to `pdf-info-search-regexp'. + +Valid compile-flags are: + +newline-crlf, newline-lf, newline-cr, dupnames, optimize, +no-auto-capture, raw, ungreedy, dollar-endonly, anchored, +extended, dotall, multiline and caseless. + +Note that the last one, caseless, is handled special, as it is +always added if `case-fold-search' is non-nil. + +And valid match-flags: + +match-anchored, match-notbol, match-noteol, match-notempty, +match-partial, match-newline-cr, match-newline-lf, +match-newline-crlf and match-newline-any. + +See the glib documentation at url +`https://developer.gnome.org/glib/stable/glib-Perl-compatible-regular-expressions.html'.") + +(defun pdf-info-search-regexp (pcre &optional pages + no-error + file-or-buffer) + "Search for a PCRE on PAGES of document FILE-OR-BUFFER. + +See `pdf-info-normalize-page-range' for valid PAGES formats and +`pdf-info-search-string' for its return value. + +Uses the flags in `pdf-info-regexp-flags', which see. If +`case-fold-search' is non-nil, the caseless flag is added. + +If NO-ERROR is non-nil, catch errors due to invalid regexps and +return nil. If it is the symbol `invalid-regexp', then re-signal +this kind of error as a `invalid-regexp' error." + + (cl-labels ((orflags (flags alist) + (cl-reduce + (lambda (v flag) + (let ((n + (cdr (assq flag alist)))) + (if n (logior n v) v))) + (cons 0 flags)))) + (let ((pages (pdf-info-normalize-page-range pages))) + (condition-case err + (pdf-info-query + 'search-regexp + (pdf-info--normalize-file-or-buffer file-or-buffer) + (car pages) + (cdr pages) + pcre + (orflags `(,(if case-fold-search + 'caseless) + ,@pdf-info-regexp-flags) + (pdf-info-regexp-compile-flags)) + (orflags pdf-info-regexp-flags + (pdf-info-regexp-match-flags))) + (error + (let ((re + (concat "\\`epdfinfo: *Invalid *regexp: *" + ;; glib error + "\\(?:Error while compiling regular expression" + " *%s *\\)?\\(.*\\)"))) + (if (or (null no-error) + (not (string-match + (format re (regexp-quote pcre)) + (cadr err)))) + (signal (car err) (cdr err)) + (if (eq no-error 'invalid-regexp) + (signal 'invalid-regexp + (list (match-string 1 (cadr err)))))))))))) + +(defun pdf-info-pagelinks (page &optional file-or-buffer) + "Return a list of links on PAGE in document FILE-OR-BUFFER. + +This function returns a list of alists with the following keys. +EDGES represents the relative bounding-box of the link , TYPE is +the type of the action, TITLE is a, possibly empty, name for this +action. + +TYPE may be one of + +goto-dest -- This is a internal link to some page. Each element +contains additional keys PAGE and TOP, where PAGE is the page of +the link and TOP its vertical position. + +goto-remote -- This a external link to some document. Same as +goto-dest, with an additional FILENAME of the external PDF. + +uri -- A link in form of some URI. Alist contains additional key +URI. + +In the first two cases, PAGE may be 0 and TOP nil, which means +these data is unspecified." + (cl-check-type page natnum) + (pdf-info-query + 'pagelinks + (pdf-info--normalize-file-or-buffer file-or-buffer) + page)) + +(defun pdf-info-number-of-pages (&optional file-or-buffer) + "Return the number of pages in document FILE-OR-BUFFER." + (pdf-info-query 'number-of-pages + (pdf-info--normalize-file-or-buffer + file-or-buffer))) + +(defun pdf-info-outline (&optional file-or-buffer) + "Return the PDF outline of document FILE-OR-BUFFER. + +This function returns a list of alists like `pdf-info-pagelinks'. +Additionally every alist has a DEPTH (>= 1) entry with the depth +of this element in the tree." + + (pdf-info-query + 'outline + (pdf-info--normalize-file-or-buffer file-or-buffer))) + +(defun pdf-info-gettext (page edges &optional selection-style + file-or-buffer) + "Get text on PAGE according to EDGES. + +EDGES should contain relative coordinates. The selection may +extend over multiple lines, which works similar to a Emacs +region. SELECTION-STYLE may be one of glyph, word or line and +determines the smallest unit of the selected region. + +Return the text contained in the selection." + + (pdf-info-query + 'gettext + (pdf-info--normalize-file-or-buffer file-or-buffer) + page + (mapconcat 'number-to-string edges " ") + (cl-case selection-style + (glyph 0) + (word 1) + (line 2) + (t 0)))) + +(defun pdf-info-getselection (page edges &optional selection-style + file-or-buffer) + "Return the edges of the selection EDGES on PAGE. + +Arguments are the same as for `pdf-info-gettext'. Return a list +of edges corresponding to the text that would be returned by the +aforementioned function, when called with the same arguments." + + (pdf-info-query + 'getselection + (pdf-info--normalize-file-or-buffer file-or-buffer) + page + (mapconcat 'number-to-string edges " ") + (cl-case selection-style + (glyph 0) + (word 1) + (line 2) + (t 0)))) + +(defun pdf-info-textregions (page &optional file-or-buffer) + "Return a list of edges describing PAGE's text-layout." + (pdf-info-getselection + page '(0 0 1 1) 'glyph file-or-buffer)) + +(defun pdf-info-charlayout (page &optional edges-or-pos file-or-buffer) + "Return the layout of characters of PAGE in/at EDGES-OR-POS. + +Returns a list of elements \(CHAR . \(LEFT TOP RIGHT BOT\)\) mapping +character to their corresponding relative bounding-boxes. + +EDGES-OR-POS may be a region \(LEFT TOP RIGHT BOT\) restricting +the returned value to include only characters fully contained in +it. Or a cons \(LEFT . TOP\) which means to only include the +character at this position. In this case the return value +contains at most one element." + + ;; FIXME: Actually returns \(CHAR . LEFT ...\). + + (unless edges-or-pos + (setq edges-or-pos '(0 0 1 1))) + (when (numberp (cdr edges-or-pos)) + (setq edges-or-pos (list (car edges-or-pos) + (cdr edges-or-pos) + -1 -1))) + (pdf-info-query + 'charlayout + (pdf-info--normalize-file-or-buffer file-or-buffer) + page + (mapconcat 'number-to-string edges-or-pos " "))) + +(defun pdf-info-pagesize (page &optional file-or-buffer) + "Return the size of PAGE as a cons \(WIDTH . HEIGHT\) + +The size is in PDF points." + (pdf-info-query + 'pagesize + (pdf-info--normalize-file-or-buffer file-or-buffer) + page)) + +(defun pdf-info-running-p () + "Return non-nil, if the server is running." + (and (processp (pdf-info-process)) + (eq (process-status (pdf-info-process)) + 'run))) + +(defun pdf-info-quit (&optional timeout) + "Quit the epdfinfo server. + +This blocks until all outstanding requests are answered. Unless +TIMEOUT is non-nil, in which case we wait at most TIMEOUT seconds +before killing the server." + (cl-check-type timeout (or null number)) + (when (pdf-info-running-p) + (let ((pdf-info-asynchronous + (if timeout (lambda (&rest _)) + pdf-info-asynchronous))) + (pdf-info-query 'quit) + (when timeout + (setq timeout (+ (float-time) (max 0 timeout))) + (while (and (pdf-info-running-p) + (> timeout (float-time))) + (accept-process-output (pdf-info-process) 0.5 nil t))))) + (when (processp (pdf-info-process)) + (tq-close pdf-info--queue)) + (setq pdf-info--queue nil)) + +(defun pdf-info-kill () + "Kill the epdfinfo server. + +Immediately delete the server process, see also `pdf-info-quit', +for a more sane way to exit the program." + (when (processp (pdf-info-process)) + (tq-close pdf-info--queue)) + (setq pdf-info--queue nil)) + +(defun pdf-info-getannots (&optional pages file-or-buffer) + "Return the annotations on PAGE. + +See `pdf-info-normalize-page-range' for valid PAGES formats. + +This function returns the annotations for PAGES as a list of +alists. Each element of this list describes one annotation and +contains the following keys. + +page - Its page number. +edges - Its area. +type - A symbol describing the annotation's type. +id - A document-wide unique symbol referencing this annotation. +flags - Its flags, binary encoded. +color - Its color in standard Emacs notation. +contents - The text of this annotation. +modified - The last modification date of this annotation. + +Additionally, if the annotation is a markup annotation, the +following keys are present. + +label - The annotation's label. +subject - The subject addressed. +opacity - The level of relative opacity. +popup-edges - The edges of a associated popup window or nil. +popup-is-open - Whether this window should be displayed open. +created - The date this markup annotation was created. + +If the annotation is also a markup text annotation, the alist +contains the following keys. + +text-icon - A string describing the purpose of this annotation. +text-state - A string, e.g. accepted or rejected." ;FIXME: Use symbols ? + + (let ((pages (pdf-info-normalize-page-range pages))) + (pdf-info-query + 'getannots + (pdf-info--normalize-file-or-buffer file-or-buffer) + (car pages) + (cdr pages)))) + +(defun pdf-info-getannot (id &optional file-or-buffer) + "Return the annotation for ID. + +ID should be a symbol, which was previously returned in a +`pdf-info-getannots' query. Signal an error, if an annotation +with ID is not available. + +See `pdf-info-getannots' for the kind of return value of this +function." + (pdf-info-query + 'getannot + (pdf-info--normalize-file-or-buffer file-or-buffer) + id)) + +(defun pdf-info-addannot (page edges type &optional file-or-buffer &rest markup-edges) + "Add a new annotation to PAGE with EDGES of TYPE. + +FIXME: TYPE may be one of `text', `markup-highlight', ... . +FIXME: -1 = 24 +See `pdf-info-getannots' for the kind of value of this function +returns." + (pdf-info-assert-writable-annotations) + (when (consp file-or-buffer) + (push file-or-buffer markup-edges) + (setq file-or-buffer nil)) + (apply + 'pdf-info-query + 'addannot + (pdf-info--normalize-file-or-buffer file-or-buffer) + page + type + (mapconcat 'number-to-string edges " ") + (mapcar (lambda (me) + (mapconcat 'number-to-string me " ")) + markup-edges))) + +(defun pdf-info-delannot (id &optional file-or-buffer) + "Delete the annotation with ID in FILE-OR-BUFFER. + +ID should be a symbol, which was previously returned in a +`pdf-info-getannots' query. Signal an error, if annotation ID +does not exist." + (pdf-info-assert-writable-annotations) + (pdf-info-query + 'delannot + (pdf-info--normalize-file-or-buffer file-or-buffer) + id)) + +(defun pdf-info-mvannot (id edges &optional file-or-buffer) + "Move/Resize annotation ID to fit EDGES. + +ID should be a symbol, which was previously returned in a +`pdf-info-getannots' query. Signal an error, if annotation ID +does not exist. + +EDGES should be a list \(LEFT TOP RIGHT BOT\). RIGHT and/or BOT +may also be negative, which means to keep the width +resp. height." + (pdf-info-editannot id `((edges . ,edges)) file-or-buffer)) + +(defun pdf-info-editannot (id modifications &optional file-or-buffer) + "Edit annotation ID, applying MODIFICATIONS. + +ID should be a symbol, which was previously returned in a +`pdf-info-getannots' query. + +MODIFICATIONS is an alist of properties and their new values. + +The server must support modifying annotations for this to work." + + (pdf-info-assert-writable-annotations) + (let ((edits + (mapcar + (lambda (elt) + (cl-case (car elt) + (color + (list (car elt) + (pdf-util-hexcolor (cdr elt)))) + (edges + (list (car elt) + (mapconcat 'number-to-string (cdr elt) " "))) + ((popup-is-open is-open) + (list (car elt) (if (cdr elt) 1 0))) + (t + (list (car elt) (cdr elt))))) + modifications))) + (apply 'pdf-info-query + 'editannot + (pdf-info--normalize-file-or-buffer file-or-buffer) + id + (apply 'append edits)))) + +(defun pdf-info-save (&optional file-or-buffer) + "Save FILE-OR-BUFFER. + +This saves the document to a new temporary file, which is +returned and owned by the caller." + (pdf-info-assert-writable-annotations) + (pdf-info-query + 'save + (pdf-info--normalize-file-or-buffer file-or-buffer))) + +(defun pdf-info-getattachment-from-annot (id &optional do-save file-or-buffer) + "Return the attachment associated with annotation ID. + +ID should be a symbol which was previously returned in a +`pdf-info-getannots' query, and referencing an attachment of type +`file', otherwise an error is signaled. + +See `pdf-info-getattachments' for the kind of return value of this +function and the meaning of DO-SAVE." + + (pdf-info-query + 'getattachment-from-annot + (pdf-info--normalize-file-or-buffer file-or-buffer) + id + (if do-save 1 0))) + +(defun pdf-info-getattachments (&optional do-save file-or-buffer) + "Return all document level attachments. + +If DO-SAVE is non-nil, save the attachments data to a local file, +which is then owned by the caller, see below. + +This function returns a list of alists, where every element +contains the following keys. All values, except for id, may be +nil, i.e. not present. + +id - A symbol uniquely identifying this attachment. +filename - The filename of this attachment. +description - A description of this attachment. +size - The size in bytes. +modified - The last modification date. +created - The date of creation. +checksum - A MD5 checksum of this attachment's data. +file - The name of a tempfile containing the data (only present if + DO-SAVE is non-nil)." + + (pdf-info-query + 'getattachments + (pdf-info--normalize-file-or-buffer file-or-buffer) + (if do-save 1 0))) + +(defun pdf-info-synctex-forward-search (source &optional line column file-or-buffer) + "Perform a forward search with synctex. + +SOURCE should be a LaTeX buffer or the absolute filename of a +corresponding file. LINE and COLUMN represent the position in +the buffer or file. Finally FILE-OR-BUFFER corresponds to the +PDF document. + +Returns an alist with entries PAGE and relative EDGES describing +the position in the PDF document corresponding to the SOURCE +location." + + (let ((source (if (buffer-live-p (get-buffer source)) + (buffer-file-name (get-buffer source)) + source))) + (pdf-info-query + 'synctex-forward-search + (pdf-info--normalize-file-or-buffer file-or-buffer) + source + (or line 1) + (or column 1)))) + +(defun pdf-info-synctex-backward-search (page &optional x y file-or-buffer) + "Perform a backward search with synctex. + +Find the source location corresponding to the coordinates +\(X . Y\) on PAGE in FILE-OR-BUFFER. + +Returns an alist with entries FILENAME, LINE and COLUMN." + + + (pdf-info-query + 'synctex-backward-search + (pdf-info--normalize-file-or-buffer file-or-buffer) + page + (or x 0) + (or y 0))) + +(defun pdf-info-renderpage (page width &optional file-or-buffer &rest commands) + "Render PAGE with width WIDTH. + +Return the data of the corresponding PNG image." + (when (keywordp file-or-buffer) + (push file-or-buffer commands) + (setq file-or-buffer nil)) + (apply 'pdf-info-query + 'renderpage + (pdf-info--normalize-file-or-buffer file-or-buffer) + page + (* width (pdf-util-frame-scale-factor)) + (let (transformed) + (while (cdr commands) + (let ((kw (pop commands)) + (value (pop commands))) + (setq value + (cl-case kw + ((:crop-to :highlight-line :highlight-region :highlight-text) + (mapconcat 'number-to-string value " ")) + ((:foreground :background) + (pdf-util-hexcolor value)) + (:alpha + (number-to-string value)) + (otherwise value))) + (push kw transformed) + (push value transformed))) + (when commands + (error "Keyword is missing a value: %s" (car commands))) + (nreverse transformed)))) + +(defun pdf-info-renderpage-text-regions (page width single-line-p + &optional file-or-buffer + &rest regions) + "Highlight text on PAGE with width WIDTH using REGIONS. + +REGIONS is a list determining foreground and background color and +the regions to render. So each element should look like \(FG BG +\(LEFT TOP RIGHT BOT\) \(LEFT TOP RIGHT BOT\) ... \) . The +rendering is text-aware. + +If SINGLE-LINE-P is non-nil, the edges in REGIONS are each +supposed to be limited to a single line in the document. Setting +this, if applicable, avoids rendering problems. + +For the other args see `pdf-info-renderpage'. + +Return the data of the corresponding PNG image." + + (when (consp file-or-buffer) + (push file-or-buffer regions) + (setq file-or-buffer nil)) + + (apply 'pdf-info-renderpage + page width file-or-buffer + (apply 'append + (mapcar (lambda (elt) + `(:foreground ,(pop elt) + :background ,(pop elt) + ,@(cl-mapcan (lambda (edges) + `(,(if single-line-p + :highlight-line + :highlight-text) + ,edges)) + elt))) + regions)))) + +(defun pdf-info-renderpage-highlight (page width + &optional file-or-buffer + &rest regions) + "Highlight regions on PAGE with width WIDTH using REGIONS. + +REGIONS is a list determining the background color, a alpha value +and the regions to render. So each element should look like \(FILL-COLOR +STROKE-COLOR ALPHA \(LEFT TOP RIGHT BOT\) \(LEFT TOP RIGHT BOT\) ... \) +. + +For the other args see `pdf-info-renderpage'. + +Return the data of the corresponding PNG image." + + (when (consp file-or-buffer) + (push file-or-buffer regions) + (setq file-or-buffer nil)) + + (apply 'pdf-info-renderpage + page width file-or-buffer + (apply 'append + (mapcar (lambda (elt) + `(:background ,(pop elt) + :foreground ,(pop elt) + :alpha ,(pop elt) + ,@(cl-mapcan (lambda (edges) + `(:highlight-region ,edges)) + elt))) + regions)))) + +(defun pdf-info-boundingbox (page &optional file-or-buffer) + "Return a bounding-box for PAGE. + +Returns a list \(LEFT TOP RIGHT BOT\)." + + (pdf-info-query + 'boundingbox + (pdf-info--normalize-file-or-buffer file-or-buffer) + page)) + +(defun pdf-info-getoptions (&optional file-or-buffer) + (pdf-info-query + 'getoptions + (pdf-info--normalize-file-or-buffer file-or-buffer))) + +(defun pdf-info-setoptions (&optional file-or-buffer &rest options) + (when (symbolp file-or-buffer) + (push file-or-buffer options) + (setq file-or-buffer nil)) + (unless (= (% (length options) 2) 0) + (error "Missing a option value")) + (apply 'pdf-info-query + 'setoptions + (pdf-info--normalize-file-or-buffer file-or-buffer) + (let (soptions) + (while options + (let ((key (pop options)) + (value (pop options))) + (unless (and (keywordp key) + (not (eq key :))) + (error "Keyword expected: %s" key)) + (cl-case key + ((:render/foreground :render/background) + (push (pdf-util-hexcolor value) + soptions)) + ((:render/usecolors :render/printed) + (push (if value 1 0) soptions)) + (t (push value soptions))) + (push key soptions))) + soptions))) + + + +(defun pdf-info-pagelabels (&optional file-or-buffer) + "Return a list of pagelabels. + +Returns a list of strings corresponding to the labels of the +pages in FILE-OR-BUFFER." + + (pdf-info-query + 'pagelabels + (pdf-info--normalize-file-or-buffer file-or-buffer))) + +(defun pdf-info-ping (&optional message) + "Ping the server using MESSAGE. + +Returns MESSAGE, which defaults to \"pong\"." + (pdf-info-query 'ping (or message "pong"))) + +(provide 'pdf-info) + +;;; pdf-info.el ends here diff --git a/elpa/pdf-tools-20211110.513/pdf-info.elc b/elpa/pdf-tools-20211110.513/pdf-info.elc Binary files differ. diff --git a/elpa/pdf-tools-20211110.513/pdf-isearch.el b/elpa/pdf-tools-20211110.513/pdf-isearch.el @@ -0,0 +1,832 @@ +;;; pdf-isearch.el --- Isearch in pdf buffers. -*- lexical-binding: t -*- + +;; Copyright (C) 2013, 2014 Andreas Politz + +;; Author: Andreas Politz <politza@fh-trier.de> +;; Keywords: files, multimedia + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: +;; +;;; Todo: +;; +;; * Add the possibility to limit the search to a range of pages. + +(require 'cl-lib) +(require 'pdf-util) +(require 'pdf-info) +(require 'pdf-misc) +(require 'pdf-view) +(require 'pdf-cache) +(require 'let-alist) + +;;; Code: + + + +;; * ================================================================== * +;; * Customizations +;; * ================================================================== * + +(defgroup pdf-isearch nil + "Isearch in pdf buffers." + :group 'pdf-tools) + +(defface pdf-isearch-match + '((((background dark)) (:inherit isearch)) + (((background light)) (:inherit isearch))) + "Face used to determine the colors of the current match." + :group 'pdf-isearch + :group 'pdf-tools-faces) + +(defface pdf-isearch-lazy + '((((background dark)) (:inherit lazy-highlight)) + (((background light)) (:inherit lazy-highlight))) + "Face used to determine the colors of non-current matches." + :group 'pdf-isearch + :group 'pdf-tools-faces) + +(defface pdf-isearch-batch + '((((background dark)) (:inherit match)) + (((background light)) (:inherit match))) + "Face used to determine the colors in `pdf-isearch-batch-mode'." + :group 'pdf-isearch + :group 'pdf-tools-faces) + +(defcustom pdf-isearch-hyphenation-character "-­" + "Characters used as hyphens when word searching." + :group 'pdf-isearch + :type 'string) + +(defvar pdf-isearch-search-fun-function nil + "Search function used when searching. + +Like `isearch-search-fun-function', though it should return a +function \(FN STRING &optional PAGES\), which in turn should +return a result like `pdf-info-search-regexp'.") + + +;; * ================================================================== * +;; * Internal Variables +;; * ================================================================== * + +(defvar-local pdf-isearch-current-page nil + "The page that is currently searched.") + +(defvar-local pdf-isearch-current-match nil + "A list ((LEFT TOP RIGHT BOT) ...) of the current match or nil. + +A match may contain more than one edges-element, e.g. when regexp +searching across multiple lines.") + +(defvar-local pdf-isearch-current-matches nil + "A list of matches of the last search.") + +(defvar-local pdf-isearch-current-parameter nil + "A list of search parameter \(search-string regex-p case-fold word-search\).") + + +;; * ================================================================== * +;; * Modes +;; * ================================================================== * + +(declare-function pdf-occur "pdf-occur.el") +(declare-function pdf-sync-backward-search "pdf-sync.el") + +(defvar pdf-isearch-minor-mode-map + (let ((kmap (make-sparse-keymap))) + (define-key kmap [remap occur] 'pdf-occur) + kmap) + "Keymap used in `pdf-isearch-minor-mode'.") + +(defvar pdf-isearch-active-mode-map + (let ((kmap (make-sparse-keymap))) + (set-keymap-parent kmap isearch-mode-map) + (define-key kmap (kbd "C-d") 'pdf-view-dark-minor-mode) + (define-key kmap (kbd "C-b") 'pdf-isearch-batch-mode) + (define-key kmap (kbd "M-s o") 'pdf-isearch-occur) + (define-key kmap (kbd "M-s s") 'pdf-isearch-sync-backward) + kmap) + "Keymap used in `pdf-isearch-active-mode'. + +This keymap is used, when isearching in PDF buffers. Its parent +keymap is `isearch-mode-map'.") + +(put 'image-scroll-up 'isearch-scroll t) +(put 'image-scroll-down 'isearch-scroll t) + +(define-minor-mode pdf-isearch-active-mode + "" + :group 'pdf-isearch + (cond + (pdf-isearch-active-mode + (set (make-local-variable 'isearch-mode-map) + pdf-isearch-active-mode-map) + (setq overriding-terminal-local-map + isearch-mode-map)) + (t + ;;(setq overriding-terminal-local-map nil) ? + (kill-local-variable 'isearch-mode-map)))) + +;;;###autoload +(define-minor-mode pdf-isearch-minor-mode + "Isearch mode for PDF buffer. + +When this mode is enabled \\[isearch-forward], among other keys, +starts an incremental search in this PDF document. Since this mode +uses external programs to highlight found matches via +image-processing, proceeding to the next match may be slow. + +Therefore two isearch behaviours have been defined: Normal isearch and +batch mode. The later one is a minor mode +\(`pdf-isearch-batch-mode'\), which when activated inhibits isearch +from stopping at and highlighting every single match, but rather +display them batch-wise. Here a batch means a number of matches +currently visible in the selected window. + +The kind of highlighting is determined by three faces +`pdf-isearch-match' \(for the current match\), `pdf-isearch-lazy' +\(for all other matches\) and `pdf-isearch-batch' \(when in batch +mode\), which see. + +Colors may also be influenced by the minor-mode +`pdf-view-dark-minor-mode'. If this is minor mode enabled, each face's +dark colors, are used (see e.g. `frame-background-mode'), instead +of the light ones. + +\\{pdf-isearch-minor-mode-map} +While in `isearch-mode' the following keys are available. Note +that not every isearch command work as expected. + +\\{pdf-isearch-active-mode-map}" + :group 'pdf-isearch + (pdf-util-assert-pdf-buffer) + (cond + (pdf-isearch-minor-mode + (when (boundp 'character-fold-search) + (setq-local character-fold-search nil)) + (set (make-local-variable 'isearch-search-fun-function) + (lambda nil 'pdf-isearch-search-function)) + (set (make-local-variable 'isearch-push-state-function) + 'pdf-isearch-push-state-function) + (set (make-local-variable 'isearch-wrap-function) + 'pdf-isearch-wrap-function) + (set (make-local-variable 'isearch-lazy-highlight) nil) + ;; Make our commands work in isearch-mode. + (set (make-local-variable 'isearch-allow-scroll) t) + (set (make-local-variable 'search-exit-option) + ;; This maybe edit or t, but edit would suppress our cmds + ;; in isearch-other-meta-char. + (not (not search-exit-option))) + ;; FIXME: Die Variable imagemagick-render-type entweder an anderer + ;; Stelle global setzen oder nur irgendwo auf den + ;; Performancegewinn hinweisen. + (when (and (boundp 'imagemagick-render-type) + (= 0 imagemagick-render-type)) + ;; This enormously speeds up rendering. + (setq imagemagick-render-type 1)) + (add-hook 'isearch-mode-hook 'pdf-isearch-mode-initialize nil t) + (add-hook 'isearch-mode-end-hook 'pdf-isearch-mode-cleanup nil t) + (add-hook 'isearch-update-post-hook 'pdf-isearch-update nil t)) + (t + (when (boundp 'character-fold-search) + (kill-local-variable 'character-fold-search)) + (kill-local-variable 'search-exit-option) + (kill-local-variable 'isearch-allow-scroll) + (kill-local-variable 'isearch-search-fun-function) + (kill-local-variable 'isearch-push-state-function) + (kill-local-variable 'isearch-wrap-function) + (kill-local-variable 'isearch-lazy-highlight) + (remove-hook 'isearch-update-post-hook 'pdf-isearch-update t) + (remove-hook 'isearch-mode-hook 'pdf-isearch-mode-initialize t) + (remove-hook 'isearch-mode-end-hook 'pdf-isearch-mode-cleanup t)))) + +(define-minor-mode pdf-isearch-batch-mode + "Isearch PDF documents batch-wise. + +If this mode is enabled, isearching does not stop at every match, +but rather moves to the next one not currently visible. This +behaviour is much faster than ordinary isearch, since far less +different images have to be displayed." + :group 'pdf-isearch + (when isearch-mode + (pdf-isearch-redisplay) + (pdf-isearch-message + (if pdf-isearch-batch-mode "batch mode" "isearch mode")))) + + + +;; * ================================================================== * +;; * Isearch interface +;; * ================================================================== * + +(defvar pdf-isearch-filter-matches-function nil + "A function for filtering isearch matches. + +The function receives one argument: a list of matches, each +being a list of edges. It should return a subset of this list. +Edge coordinates are in image-space.") + +(defvar pdf-isearch-narrow-to-page nil + "Non-nil, if the search should be limited to the current page.") + +(defun pdf-isearch-search-function (string &rest _) + "Search for STRING in the current PDF buffer. + +This is a Isearch interface function." + (when (> (length string) 0) + (let ((same-search-p (pdf-isearch-same-search-p)) + (oldpage pdf-isearch-current-page) + (matches (pdf-isearch-search-page string)) + next-match) + ;; matches is a list of list of edges ((x0 y1 x1 y2) ...), + ;; sorted top to bottom ,left to right. Coordinates are in image + ;; space. + (unless isearch-forward + (setq matches (reverse matches))) + (when pdf-isearch-filter-matches-function + (setq matches (funcall pdf-isearch-filter-matches-function matches))) + ;; Where to go next ? + (setq pdf-isearch-current-page (pdf-view-current-page) + pdf-isearch-current-matches matches + next-match + (pdf-isearch-next-match + oldpage pdf-isearch-current-page + pdf-isearch-current-match matches + same-search-p + isearch-forward) + pdf-isearch-current-parameter + (list string isearch-regexp + isearch-case-fold-search isearch-word)) + (cond + (next-match + (setq pdf-isearch-current-match next-match) + (pdf-isearch-hl-matches next-match matches) + (pdf-isearch-focus-match next-match) + ;; Don't get off track. + (when (or (and (bobp) (not isearch-forward)) + (and (eobp) isearch-forward)) + (goto-char (1+ (/ (buffer-size) 2)))) + ;; Signal success to isearch. + (if isearch-forward + (re-search-forward ".") + (re-search-backward "."))) + ((and (not pdf-isearch-narrow-to-page) + (not (pdf-isearch-empty-match-p matches))) + (let ((next-page (pdf-isearch-find-next-matching-page + string pdf-isearch-current-page t))) + (when next-page + (pdf-view-goto-page next-page) + (pdf-isearch-search-function string)))))))) + +(defun pdf-isearch-push-state-function () + "Push the current search state. + +This is a Isearch interface function." + (let ((hscroll (window-hscroll)) + (vscroll (window-vscroll)) + (parms pdf-isearch-current-parameter) + (matches pdf-isearch-current-matches) + (match pdf-isearch-current-match) + (page pdf-isearch-current-page)) + (lambda (_state) + (setq pdf-isearch-current-parameter parms + pdf-isearch-current-matches matches + pdf-isearch-current-match match + pdf-isearch-current-page page) + + (pdf-view-goto-page pdf-isearch-current-page) + (when pdf-isearch-current-match + (pdf-isearch-hl-matches + pdf-isearch-current-match + pdf-isearch-current-matches)) + (image-set-window-hscroll hscroll) + (image-set-window-vscroll vscroll)))) + +(defun pdf-isearch-wrap-function () + "Go to first or last page. + +This is a Isearch interface function." + (let ((page (if isearch-forward + 1 + (pdf-cache-number-of-pages)))) + (unless (or pdf-isearch-narrow-to-page + (= page (pdf-view-current-page))) + (pdf-view-goto-page page) + (let ((next-screen-context-lines 0)) + (if (= page 1) + (image-scroll-down) + (image-scroll-up))))) + (setq pdf-isearch-current-match nil)) + +(defun pdf-isearch-mode-cleanup () + "Cleanup after exiting Isearch. + +This is a Isearch interface function." + (pdf-isearch-active-mode -1) + (pdf-view-redisplay)) + +(defun pdf-isearch-mode-initialize () + "Initialize isearching. + +This is a Isearch interface function." + (pdf-isearch-active-mode 1) + (setq pdf-isearch-current-page (pdf-view-current-page) + pdf-isearch-current-match nil + pdf-isearch-current-matches nil + pdf-isearch-current-parameter nil) + (goto-char (1+ (/ (buffer-size) 2)))) + +(defun pdf-isearch-same-search-p (&optional ignore-search-string-p) + "Return non-nil, if search parameter have not changed. + +Parameter inspected are `isearch-string' (unless +IGNORE-SEARCH-STRING-P is t) and `isearch-case-fold-search'. If +there was no previous search, this function returns t." + (or (null pdf-isearch-current-parameter) + (let ((parameter (list isearch-string + isearch-regexp + isearch-case-fold-search + isearch-word))) + (if ignore-search-string-p + (equal (cdr pdf-isearch-current-parameter) + (cdr parameter)) + (equal pdf-isearch-current-parameter + parameter))))) + +(defun pdf-isearch-next-match (last-page this-page last-match + all-matches continued-p + forward-p) + "Determine the next match." + (funcall (if pdf-isearch-batch-mode + 'pdf-isearch-next-match-batch + 'pdf-isearch-next-match-isearch) + last-page this-page last-match + all-matches continued-p forward-p)) + +(defun pdf-isearch-focus-match (current-match) + "Make the CURRENT-MATCH visible in the window." + (funcall (if pdf-isearch-batch-mode + 'pdf-isearch-focus-match-batch + 'pdf-isearch-focus-match-isearch) + current-match)) + +(defun pdf-isearch-redisplay () + "Redisplay the current highlighting." + (pdf-isearch-hl-matches pdf-isearch-current-match + pdf-isearch-current-matches)) + +(defun pdf-isearch-update () + "Update search and redisplay, if necessary." + (unless (pdf-isearch-same-search-p t) + (setq pdf-isearch-current-parameter + (list isearch-string isearch-regexp + isearch-case-fold-search isearch-word) + pdf-isearch-current-matches + (pdf-isearch-search-page isearch-string)) + (pdf-isearch-redisplay))) + +(defun pdf-isearch-message (fmt &rest args) + "Like `message', but Isearch friendly." + (unless args (setq args (list fmt) fmt "%s")) + (let ((msg (apply 'format fmt args))) + (if (cl-some (lambda (buf) + (buffer-local-value 'isearch-mode buf)) + (mapcar 'window-buffer (window-list))) + (let ((isearch-message-suffix-add + (format " [%s]" msg))) + (isearch-message) + (sit-for 1)) + (message "%s" msg)))) + +(defun pdf-isearch-empty-match-p (matches) + (and matches + (cl-every + (lambda (match) + (cl-every (lambda (edges) + (cl-every 'zerop edges)) + match)) + matches))) + +(defun pdf-isearch-occur () + "Run `occur' using the last search string or regexp." + (interactive) + (let ((case-fold-search isearch-case-fold-search) + (regexp + (cond + ((functionp isearch-word) + (funcall isearch-word isearch-string)) + (isearch-word (pdf-isearch-word-search-regexp + isearch-string nil + pdf-isearch-hyphenation-character)) + (isearch-regexp isearch-string)))) + (save-selected-window + (pdf-occur (or regexp isearch-string) regexp)) + (isearch-message))) + +(defun pdf-isearch-sync-backward () + "Visit the source of the beginning of the current match." + (interactive) + (pdf-util-assert-pdf-window) + (unless pdf-isearch-current-match + (user-error "No current or recent match")) + (when isearch-mode + (isearch-exit)) + (cl-destructuring-bind (left top _right _bot) + (car pdf-isearch-current-match) + (pdf-sync-backward-search left top))) + + +;; * ================================================================== * +;; * Interface to epdfinfo +;; * ================================================================== * + +(defun pdf-isearch-search-page (string &optional page) + "Search STRING on PAGE in the current window. + +Returns a list of edges (LEFT TOP RIGHT BOTTOM) in PDF +coordinates, sorted top to bottom, then left to right." + + (unless page (setq page (pdf-view-current-page))) + (mapcar (lambda (match) + (let-alist match + (pdf-util-scale-relative-to-pixel .edges 'round))) + (let ((case-fold-search isearch-case-fold-search)) + (funcall (pdf-isearch-search-fun) + string page)))) + +(defun pdf-isearch-search-fun () + (funcall (or pdf-isearch-search-fun-function + 'pdf-isearch-search-fun-default))) + +(defun pdf-isearch-search-fun-default () + "Return default functions to use for the search." + (cond + ((eq isearch-word t) + (lambda (string &optional pages) + ;; Use lax versions to not fail at the end of the word while + ;; the user adds and removes characters in the search string + ;; (or when using nonincremental word isearch) + (let ((lax (not (or isearch-nonincremental + (null (car isearch-cmds)) + (eq (length isearch-string) + (length (isearch--state-string + (car isearch-cmds)))))))) + (pdf-info-search-regexp + (pdf-isearch-word-search-regexp + string lax pdf-isearch-hyphenation-character) + pages 'invalid-regexp)))) + (isearch-regexp + (lambda (string &optional pages) + (pdf-info-search-regexp string pages 'invalid-regexp))) + (t + 'pdf-info-search-string))) + + +(defun pdf-isearch-word-search-regexp (string &optional lax hyphenization-chars) + "Return a PCRE which matches words, ignoring punctuation." + (let ((hyphenization-regexp + (and hyphenization-chars + (format "(?:[%s]\\n)?" + (replace-regexp-in-string + "[]^\\\\-]" "\\\\\\&" + hyphenization-chars t))))) + (cond + ((equal string "") "") + ((string-match-p "\\`\\W+\\'" string) "\\W+") + (t (concat + (if (string-match-p "\\`\\W" string) "\\W+" + (unless lax "\\b")) + (mapconcat (lambda (word) + (if hyphenization-regexp + (mapconcat + (lambda (ch) + (pdf-util-pcre-quote (string ch))) + (append word nil) + hyphenization-regexp) + (pdf-util-pcre-quote word))) + (split-string string "\\W+" t) "\\W+") + (if (string-match-p "\\W\\'" string) "\\W+" + (unless lax "\\b"))))))) + +(defun pdf-isearch-find-next-matching-page (string page &optional interactive-p) + "Find STRING after or before page PAGE, according to FORWARD-P. + +If INTERACTIVE-P is non-nil, give some progress feedback. +Returns the page number where STRING was found, or nil if there +is no such page." + ;; Do a exponentially expanding search. + (let* ((incr 1) + (pages (if isearch-forward + (cons (1+ page) + (1+ page)) + (cons (1- page) + (1- page)))) + (fn (pdf-isearch-search-fun)) + matched-page + reporter) + + (while (and (null matched-page) + (or (and isearch-forward + (<= (car pages) + (pdf-cache-number-of-pages))) + (and (not isearch-forward) + (>= (cdr pages) 1)))) + (let* ((case-fold-search isearch-case-fold-search) + (matches (funcall fn string pages))) + (setq matched-page + (alist-get 'page (if isearch-forward + (car matches) + (car (last matches)))))) + (setq incr (* incr 2)) + (cond (isearch-forward + (setcar pages (1+ (cdr pages))) + (setcdr pages (min (pdf-cache-number-of-pages) + (+ (cdr pages) incr)))) + (t + (setcdr pages (1- (car pages))) + (setcar pages (max 1 (- (car pages) + incr))))) + (when interactive-p + (when (and (not reporter) + (= incr 8)) ;;Don't bother right away. + (setq reporter + (apply + 'make-progress-reporter "Searching" + (if isearch-forward + (list (car pages) (pdf-cache-number-of-pages) nil 0) + (list 1 (cdr pages) nil 0))))) + (when reporter + (progress-reporter-update + reporter (if isearch-forward + (- (cdr pages) page) + (- page (car pages))))))) + matched-page)) + + + +;; * ================================================================== * +;; * Isearch Behavior +;; * ================================================================== * + +(defun pdf-isearch-next-match-isearch (last-page this-page last-match + matches same-search-p + forward) + "Default function for choosing the next match. + +Implements default isearch behaviour, i.e. it stops at every +match." + (cond + ((null last-match) + ;; Goto first match from top or bottom of the window. + (let* ((iedges (pdf-util-image-displayed-edges)) + (pos (pdf-util-with-edges (iedges) + (if forward + (list iedges-left iedges-top + iedges-left iedges-top) + (list iedges-right iedges-bot + iedges-right iedges-bot))))) + (pdf-isearch-closest-match (list pos) matches forward))) + ((not (eq last-page this-page)) + ;; First match from top-left or bottom-right of the new + ;; page. + (car matches)) + (same-search-p + ;; Next match after the last one. + (if last-match + (cadr (member last-match matches)))) + (matches + ;; Next match of new search closest to the last one. + (pdf-isearch-closest-match + last-match matches forward)))) + +(defun pdf-isearch-focus-match-isearch (match) + "Make the image area in MATCH visible in the selected window." + (pdf-util-scroll-to-edges (apply 'pdf-util-edges-union match))) + +(defun pdf-isearch-next-match-batch (last-page this-page last-match + matches same-search-p + forward-p) + "Select the next match, unseen in the current search direction." + + (if (or (null last-match) + (not same-search-p) + (not (eq last-page this-page))) + (pdf-isearch-next-match-isearch + last-page this-page last-match matches same-search-p forward-p) + (pdf-util-with-edges (match iedges) + (let ((iedges (pdf-util-image-displayed-edges))) + (car (cl-remove-if + ;; Filter matches visible on screen. + (lambda (edges) + (let ((match (apply 'pdf-util-edges-union edges))) + (and (<= match-right iedges-right) + (<= match-bot iedges-bot) + (>= match-left iedges-left) + (>= match-top iedges-top)))) + (cdr (member last-match matches)))))))) + +(defun pdf-isearch-focus-match-batch (match) + "Make the image area in MATCH eagerly visible in the selected window." + (pdf-util-scroll-to-edges (apply 'pdf-util-edges-union match) t)) + +(cl-deftype pdf-isearch-match () + `(satisfies + (lambda (match) + (cl-every (lambda (edges) + (and (consp edges) + (= (length edges) 4) + (cl-every 'numberp edges))) + match)))) + +(cl-deftype list-of (type) + `(satisfies + (lambda (l) + (and (listp l) + (cl-every (lambda (x) + (cl-typep x ',type)) + l))))) + +(defun pdf-isearch-closest-match (match matches + &optional forward-p) + "Find the nearest element to MATCH in MATCHES. + +The direction in which to look is determined by FORWARD-P. + +MATCH should be a list of edges, MATCHES a list of such element; +it is assumed to be ordered with respect to FORWARD-P." + + + (cl-check-type match pdf-isearch-match) + (cl-check-type matches (list-of pdf-isearch-match)) + (let ((matched (apply 'pdf-util-edges-union match))) + (pdf-util-with-edges (matched) + (cl-loop for next in matches do + (let ((edges (apply 'pdf-util-edges-union next))) + (pdf-util-with-edges (edges) + (when (if forward-p + (or (>= edges-top matched-bot) + (and (or (>= edges-top matched-top) + (>= edges-bot matched-bot)) + (>= edges-right matched-right))) + (or (<= edges-bot matched-top) + (and (or (<= edges-bot matched-bot) + (<= edges-top matched-top)) + (<= edges-left matched-left)))) + (cl-return next)))))))) + + + +;; * ================================================================== * +;; * Display +;; * ================================================================== * + + +(defun pdf-isearch-current-colors () + "Return the current color set. + +The return value depends on `pdf-view-dark-minor-mode' and +`pdf-isearch-batch-mode'. It is a list of four colors \(MATCH-FG +MATCH-BG LAZY-FG LAZY-BG\)." + (let ((dark-p pdf-view-dark-minor-mode)) + (cond + (pdf-isearch-batch-mode + (let ((colors (pdf-util-face-colors 'pdf-isearch-batch dark-p))) + (list (car colors) + (cdr colors) + (car colors) + (cdr colors)))) + (t + (let ((match (pdf-util-face-colors 'pdf-isearch-match dark-p)) + (lazy (pdf-util-face-colors 'pdf-isearch-lazy dark-p))) + (list (car match) + (cdr match) + (car lazy) + (cdr lazy))))))) + +(defvar pdf-isearch--hl-matches-tick 0) + +(defun pdf-isearch-hl-matches (current matches &optional occur-hack-p) + "Highlighting edges CURRENT and MATCHES." + (cl-check-type current pdf-isearch-match) + (cl-check-type matches (list-of pdf-isearch-match)) + (cl-destructuring-bind (fg1 bg1 fg2 bg2) + (pdf-isearch-current-colors) + (let* ((width (car (pdf-view-image-size))) + (page (pdf-view-current-page)) + (window (selected-window)) + (buffer (current-buffer)) + (tick (cl-incf pdf-isearch--hl-matches-tick)) + (pdf-info-asynchronous + (lambda (status data) + (when (and (null status) + (eq tick pdf-isearch--hl-matches-tick) + (buffer-live-p buffer) + (window-live-p window) + (eq (window-buffer window) + buffer)) + (with-selected-window window + (when (and (derived-mode-p 'pdf-view-mode) + (or isearch-mode + occur-hack-p) + (eq page (pdf-view-current-page))) + (pdf-view-display-image + (pdf-view-create-image data :width width)))))))) + (pdf-info-renderpage-text-regions + page width t nil + `(,fg1 ,bg1 ,@(pdf-util-scale-pixel-to-relative + current)) + `(,fg2 ,bg2 ,@(pdf-util-scale-pixel-to-relative + (apply 'append + (remove current matches)))))))) + + +;; * ================================================================== * +;; * Debug +;; * ================================================================== * + +;; The following isearch-search function is debuggable. +;; +(when nil + (defun isearch-search () + ;; Do the search with the current search string. + (if isearch-message-function + (funcall isearch-message-function nil t) + (isearch-message nil t)) + (if (and (eq isearch-case-fold-search t) search-upper-case) + (setq isearch-case-fold-search + (isearch-no-upper-case-p isearch-string isearch-regexp))) + (condition-case lossage + (let ((inhibit-point-motion-hooks + ;; FIXME: equality comparisons on functions is asking for trouble. + (and (eq isearch-filter-predicate 'isearch-filter-visible) + search-invisible)) + (inhibit-quit nil) + (case-fold-search isearch-case-fold-search) + (retry t)) + (setq isearch-error nil) + (while retry + (setq isearch-success + (isearch-search-string isearch-string nil t)) + ;; Clear RETRY unless the search predicate says + ;; to skip this search hit. + (if (or (not isearch-success) + (bobp) (eobp) + (= (match-beginning 0) (match-end 0)) + (funcall isearch-filter-predicate + (match-beginning 0) (match-end 0))) + (setq retry nil))) + (setq isearch-just-started nil) + (if isearch-success + (setq isearch-other-end + (if isearch-forward (match-beginning 0) (match-end 0))))) + + (quit (isearch-unread ?\C-g) + (setq isearch-success nil)) + + (invalid-regexp + (setq isearch-error (car (cdr lossage))) + (if (string-match + "\\`Premature \\|\\`Unmatched \\|\\`Invalid " + isearch-error) + (setq isearch-error "incomplete input"))) + + (search-failed + (setq isearch-success nil) + (setq isearch-error (nth 2 lossage))) + + ;; (error + ;; ;; stack overflow in regexp search. + ;; (setq isearch-error (format "%s" lossage))) + ) + + (if isearch-success + nil + ;; Ding if failed this time after succeeding last time. + (and (isearch--state-success (car isearch-cmds)) + (ding)) + (if (functionp (isearch--state-pop-fun (car isearch-cmds))) + (funcall (isearch--state-pop-fun (car isearch-cmds)) + (car isearch-cmds))) + (goto-char (isearch--state-point (car isearch-cmds)))))) + + +(provide 'pdf-isearch) + +;;; pdf-isearch.el ends here + +;; Local Variables: +;; byte-compile-warnings: (not obsolete) +;; End: diff --git a/elpa/pdf-tools-20211110.513/pdf-isearch.elc b/elpa/pdf-tools-20211110.513/pdf-isearch.elc Binary files differ. diff --git a/elpa/pdf-tools-20211110.513/pdf-links.el b/elpa/pdf-tools-20211110.513/pdf-links.el @@ -0,0 +1,379 @@ +;;; pdf-links.el --- Handle PDF links. -*- lexical-binding: t -*- + +;; Copyright (C) 2013, 2014 Andreas Politz + +;; Author: Andreas Politz <politza@fh-trier.de> +;; Keywords: files, multimedia + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: +;; + +(require 'pdf-info) +(require 'pdf-util) +(require 'pdf-misc) +(require 'pdf-cache) +(require 'pdf-isearch) +(require 'let-alist) +(require 'org) + +;;; Code: + + + +;; * ================================================================== * +;; * Customizations +;; * ================================================================== * + +(defgroup pdf-links nil + "Following links in PDF documents." + :group 'pdf-tools) + +(defface pdf-links-read-link + '((((background dark)) (:background "red" :foreground "yellow")) + (((background light)) (:background "red" :foreground "yellow"))) + "Face used to determine the colors when reading links." + ;; :group 'pdf-links + :group 'pdf-tools-faces) + +(defcustom pdf-links-read-link-convert-commands + '(;;"-font" "FreeMono" + "-pointsize" "%P" + "-undercolor" "%f" + "-fill" "%b" + "-draw" "text %X,%Y '%c'") + + "The commands for the convert program, when decorating links for reading. +See `pdf-util-convert' for an explanation of the format. + +Aside from the description there, two additional escape chars are +available. + +%P -- The scaled font pointsize, i.e. IMAGE-WIDTH * SCALE (See + `pdf-links-convert-pointsize-scale'). +%c -- String describing the current link key (e.g. AA, AB, + etc.)." + :group 'pdf-links + :type '(repeat string) + :link '(variable-link pdf-isearch-convert-commands) + :link '(url-link "http://www.imagemagick.org/script/convert.php")) + +(defcustom pdf-links-convert-pointsize-scale 0.01 + "The scale factor for the -pointsize convert command. + +This determines the relative size of the font, when interactively +reading links." + :group 'pdf-links + :type '(restricted-sexp :match-alternatives + ((lambda (x) (and (numberp x) + (<= x 1) + (>= x 0)))))) + +(defcustom pdf-links-browse-uri-function + 'pdf-links-browse-uri-default + "The function for handling uri links. + +This function should accept one argument, the URI to follow, and +do something with it." + :group 'pdf-links + :type 'function) + + +;; * ================================================================== * +;; * Minor Mode +;; * ================================================================== * + +(defvar pdf-links-minor-mode-map + (let ((kmap (make-sparse-keymap))) + (define-key kmap (kbd "f") 'pdf-links-isearch-link) + (define-key kmap (kbd "F") 'pdf-links-action-perform) + kmap)) + +;;;###autoload +(define-minor-mode pdf-links-minor-mode + "Handle links in PDF documents.\\<pdf-links-minor-mode-map> + +If this mode is enabled, most links in the document may be +activated by clicking on them or by pressing \\[pdf-links-action-perform] and selecting +one of the displayed keys, or by using isearch limited to +links via \\[pdf-links-isearch-link]. + +\\{pdf-links-minor-mode-map}" + :group 'pdf-links + (pdf-util-assert-pdf-buffer) + (cond + (pdf-links-minor-mode + (pdf-view-add-hotspot-function 'pdf-links-hotspots-function 0)) + (t + (pdf-view-remove-hotspot-function 'pdf-links-hotspots-function))) + (pdf-view-redisplay t)) + +(defun pdf-links-hotspots-function (page size) + "Create hotspots for links on PAGE using SIZE." + + (let ((links (pdf-cache-pagelinks page)) + (id-fmt "link-%d-%d") + (i 0) + (pointer 'hand) + hotspots) + (dolist (l links) + (let ((e (pdf-util-scale + (cdr (assq 'edges l)) size 'round)) + (id (intern (format id-fmt page + (cl-incf i))))) + (push `((rect . ((,(nth 0 e) . ,(nth 1 e)) + . (,(nth 2 e) . ,(nth 3 e)))) + ,id + (pointer + ,pointer + help-echo ,(pdf-links-action-to-string l))) + hotspots) + (local-set-key + (vector id 'mouse-1) + (lambda nil + (interactive "@") + (pdf-links-action-perform l))) + (local-set-key + (vector id t) + 'pdf-util-image-map-mouse-event-proxy))) + (nreverse hotspots))) + +(defun pdf-links-action-to-string (link) + "Return a string representation of ACTION." + (let-alist link + (concat + (cl-case .type + (goto-dest + (if (> .page 0) + (format "Goto page %d" .page) + "Destination not found")) + (goto-remote + (if (and .filename (file-exists-p .filename)) + (format "Goto %sfile '%s'" + (if (> .page 0) + (format "p.%d of " .page) + "") + .filename) + (format "Link to nonexistent file '%s'" .filename))) + (uri + (if (> (length .uri) 0) + (format "Link to uri '%s'" .uri) + (format "Link to empty uri"))) + (t (format "Unrecognized link type: %s" .type))) + (if (> (length .title) 0) + (format " (%s)" .title))))) + +;;;###autoload +(defun pdf-links-action-perform (link) + "Follow LINK, depending on its type. + +This may turn to another page, switch to another PDF buffer or +invoke `pdf-links-browse-uri-function'. + +Interactively, link is read via `pdf-links-read-link-action'. +This function displays characters around the links in the current +page and starts reading characters (ignoring case). After a +sufficient number of characters have been read, the corresponding +link's link is invoked. Additionally, SPC may be used to +scroll the current page." + (interactive + (list (or (pdf-links-read-link-action "Activate link (SPC scrolls): ") + (error "No link selected")))) + (let-alist link + (cl-case .type + ((goto-dest goto-remote) + (let ((window (selected-window))) + (cl-case .type + (goto-dest + (unless (> .page 0) + (error "Link points to nowhere"))) + (goto-remote + (unless (and .filename (file-exists-p .filename)) + (error "Link points to nonexistent file %s" .filename)) + (setq window (display-buffer + (or (find-buffer-visiting .filename) + (find-file-noselect .filename)))))) + (with-selected-window window + (when (derived-mode-p 'pdf-view-mode) + (when (> .page 0) + (pdf-view-goto-page .page)) + (when .top + ;; Showing the tooltip delays displaying the page for + ;; some reason (sit-for/redisplay don't help), do it + ;; later. + (run-with-idle-timer 0.001 nil + (lambda () + (when (window-live-p window) + (with-selected-window window + (when (derived-mode-p 'pdf-view-mode) + (pdf-util-tooltip-arrow .top))))))))))) + (uri + (funcall pdf-links-browse-uri-function .uri)) + (t + (error "Unrecognized link type: %s" .type))) + nil)) + +(defun pdf-links-read-link-action (prompt) + "Using PROMPT, interactively read a link-action. + +See `pdf-links-action-perform' for the interface." + + (pdf-util-assert-pdf-window) + (let* ((links (pdf-cache-pagelinks + (pdf-view-current-page))) + (keys (pdf-links-read-link-action--create-keys + (length links))) + (key-strings (mapcar (apply-partially 'apply 'string) + keys)) + (alist (cl-mapcar 'cons keys links)) + (size (pdf-view-image-size)) + (colors (pdf-util-face-colors + 'pdf-links-read-link pdf-view-dark-minor-mode)) + (args (list + :foreground (car colors) + :background (cdr colors) + :formats + `((?c . ,(lambda (_edges) (pop key-strings))) + (?P . ,(number-to-string + (max 1 (* (cdr size) + pdf-links-convert-pointsize-scale))))) + :commands pdf-links-read-link-convert-commands + :apply (pdf-util-scale-relative-to-pixel + (mapcar (lambda (l) (cdr (assq 'edges l))) + links))))) + (unless links + (error "No links on this page")) + (unwind-protect + (let ((image-data + (pdf-cache-get-image + (pdf-view-current-page) + (car size) (car size) 'pdf-links-read-link-action))) + (unless image-data + (setq image-data (apply 'pdf-util-convert-page args )) + (pdf-cache-put-image + (pdf-view-current-page) + (car size) image-data 'pdf-links-read-link-action)) + (pdf-view-display-image + (create-image image-data (pdf-view-image-type) t)) + (pdf-links-read-link-action--read-chars prompt alist)) + (pdf-view-redisplay)))) + +(defun pdf-links-read-link-action--read-chars (prompt alist) + (catch 'done + (let (key) + (while t + (let* ((chars (append (mapcar 'caar alist) + (mapcar 'downcase (mapcar 'caar alist)) + (list ?\s))) + (ch (read-char-choice prompt chars))) + (setq ch (upcase ch)) + (cond + ((= ch ?\s) + (when (= (window-vscroll) (image-scroll-up)) + (image-scroll-down (window-vscroll)))) + (t + (setq alist (delq nil (mapcar (lambda (elt) + (and (eq ch (caar elt)) + (cons (cdar elt) + (cdr elt)))) + alist)) + key (append key (list ch)) + prompt (concat prompt (list ch))) + (when (= (length alist) 1) + (message nil) + (throw 'done (cdar alist)))))))))) + +(defun pdf-links-read-link-action--create-keys (n) + (when (> n 0) + (let ((len (1+ (floor (log n 26)))) + keys) + (dotimes (i n) + (let (key) + (dotimes (_x len) + (push (+ (% i 26) ?A) key) + (setq i (/ i 26))) + (push key keys))) + (nreverse keys)))) + +(defun pdf-links-isearch-link () + (interactive) + (let* (quit-p + (isearch-mode-end-hook + (cons (lambda nil + (setq quit-p isearch-mode-end-hook-quit)) + isearch-mode-end-hook)) + (pdf-isearch-filter-matches-function + 'pdf-links-isearch-link-filter-matches) + (pdf-isearch-narrow-to-page t) + (isearch-message-prefix-add "(Links)") + pdf-isearch-batch-mode) + (isearch-forward) + (unless (or quit-p (null pdf-isearch-current-match)) + (let* ((page (pdf-view-current-page)) + (match (car pdf-isearch-current-match)) + (size (pdf-view-image-size)) + (links (sort (cl-remove-if + (lambda (e) + (= 0 (pdf-util-edges-intersection-area (car e) match))) + (mapcar (lambda (l) + (cons (pdf-util-scale (alist-get 'edges l) size) + l)) + (pdf-cache-pagelinks page))) + (lambda (e1 e2) + (> (pdf-util-edges-intersection-area + (alist-get 'edges e1) match) + (pdf-util-edges-intersection-area + (alist-get 'edges e2) match)))))) + (unless links + (error "No link found at this position")) + (pdf-links-action-perform (car links)))))) + +(defun pdf-links-isearch-link-filter-matches (matches) + (let ((links (pdf-util-scale + (mapcar (apply-partially 'alist-get 'edges) + (pdf-cache-pagelinks + (pdf-view-current-page))) + (pdf-view-image-size)))) + (cl-remove-if-not + (lambda (m) + (cl-some + (lambda (edges) + (cl-some (lambda (link) + (pdf-util-with-edges (link edges) + (let ((area (min (* link-width link-height) + (* edges-width edges-height)))) + (> (/ (pdf-util-edges-intersection-area edges link) + (float area)) 0.5)))) + links)) + m)) + matches))) + +(defun pdf-links-browse-uri-default (uri) + "Open the string URI using Org. + +Wraps the URI in \[\[ ... \]\] and calls `org-open-link-from-string' +on the resulting string." + (cl-check-type uri string) + (message "Opening `%s' with Org" uri) + (cond + ((fboundp 'org-link-open-from-string) + (org-link-open-from-string (format "[[%s]]" uri))) + ;; For Org 9.2 and older + ((fboundp 'org-open-link-from-string) + (org-open-link-from-string (format "[[%s]]" uri))))) + +(provide 'pdf-links) + +;;; pdf-links.el ends here diff --git a/elpa/pdf-tools-20211110.513/pdf-links.elc b/elpa/pdf-tools-20211110.513/pdf-links.elc Binary files differ. diff --git a/elpa/pdf-tools-20211110.513/pdf-loader.el b/elpa/pdf-tools-20211110.513/pdf-loader.el @@ -0,0 +1,80 @@ +;;; pdf-loader.el --- Minimal PDF Tools loader -*- lexical-binding: t; -*- + +;; Copyright (C) 2017 Andreas Politz + +;; Author: Andreas Politz <politza@hochschule-trier.de> +;; Keywords: + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; + +;;; Code: + +(defconst pdf-loader--auto-mode-alist-item + (copy-sequence "\\.[pP][dD][fF]\\'") + "The item used in `auto-mode-alist'.") + +(defconst pdf-loader--magic-mode-alist-item + (copy-sequence "%PDF") + "The item used in`magic-mode-alist'.") + + +(declare-function pdf-tools-install "pdf-tools.el") + +;;;###autoload +(defun pdf-loader-install (&optional no-query-p skip-dependencies-p + no-error-p force-dependencies-p) + "Prepare Emacs for using PDF Tools. + +This function acts as a replacement for `pdf-tools-install' and +makes Emacs load and use PDF Tools as soon as a PDF file is +opened, but not sooner. + +The arguments are passed verbatim to `pdf-tools-install', which +see." + (let ((args (list no-query-p skip-dependencies-p + no-error-p force-dependencies-p))) + (if (featurep 'pdf-tools) + (apply #'pdf-tools-install args) + (pdf-loader--install + (lambda () + (apply #'pdf-loader--load args)))))) + +(defun pdf-loader--load (&rest args) + (pdf-loader--uninstall) + (save-selected-window + (pdf-tools-install args))) + +(defun pdf-loader--install (loader) + (pdf-loader--uninstall) + (push (cons pdf-loader--auto-mode-alist-item loader) + auto-mode-alist) + (push (cons pdf-loader--magic-mode-alist-item loader) + magic-mode-alist)) + +(defun pdf-loader--uninstall () + (let ((elt (assoc pdf-loader--auto-mode-alist-item + auto-mode-alist))) + (when elt + (setq auto-mode-alist (remove elt auto-mode-alist)))) + (let ((elt (assoc pdf-loader--magic-mode-alist-item + magic-mode-alist))) + (when elt + (setq magic-mode-alist (remove elt magic-mode-alist))))) + +(provide 'pdf-loader) +;;; pdf-loader.el ends here diff --git a/elpa/pdf-tools-20211110.513/pdf-loader.elc b/elpa/pdf-tools-20211110.513/pdf-loader.elc Binary files differ. diff --git a/elpa/pdf-tools-20211110.513/pdf-macs.el b/elpa/pdf-tools-20211110.513/pdf-macs.el @@ -0,0 +1,51 @@ +;;; pdf-macs.el --- Macros for pdf-tools. -*- lexical-binding:t -*- + +;; Copyright (C) 2013 Andreas Politz + +;; Author: Andreas Politz <politza@fh-trier.de> +;; Keywords: files, doc-view, pdf + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: +;; +;;; Code: +;; + +(defmacro pdf-view-current-page (&optional window) + ;;TODO: write documentation! + `(image-mode-window-get 'page ,window)) + +(defmacro pdf-view-current-overlay (&optional window) + ;;TODO: write documentation! + `(image-mode-window-get 'overlay ,window)) + +(defmacro pdf-view-current-image (&optional window) + ;;TODO: write documentation! + `(image-mode-window-get 'image ,window)) + +(defmacro pdf-view-current-slice (&optional window) + ;;TODO: write documentation! + `(image-mode-window-get 'slice ,window)) + +(defmacro pdf-view-current-window-size (&optional window) + ;;TODO: write documentation! + `(image-mode-window-get 'window-size ,window)) + +(defmacro pdf-view-window-needs-redisplay (&optional window) + `(image-mode-window-get 'needs-redisplay ,window)) + +(provide 'pdf-macs) + +;;; pdf-macs.el ends here diff --git a/elpa/pdf-tools-20211110.513/pdf-macs.elc b/elpa/pdf-tools-20211110.513/pdf-macs.elc Binary files differ. diff --git a/elpa/pdf-tools-20211110.513/pdf-misc.el b/elpa/pdf-tools-20211110.513/pdf-misc.el @@ -0,0 +1,295 @@ +;;; pdf-misc.el --- Miscellaneous commands for PDF buffer. + +;; Copyright (C) 2013, 2014 Andreas Politz + +;; Author: Andreas Politz <politza@fh-trier.de> +;; Keywords: files, multimedia + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: +;; + + +(require 'pdf-view) +(require 'pdf-util) +(require 'imenu) + + + +(defvar pdf-misc-minor-mode-map + (let ((map (make-sparse-keymap))) + (define-key map (kbd "I") 'pdf-misc-display-metadata) + (define-key map (kbd "C-c C-p") 'pdf-misc-print-document) + map) + "Keymap used in `pdf-misc-minor-mode'.") + +;;;###autoload +(define-minor-mode pdf-misc-minor-mode + "FIXME: Not documented." + :group 'pdf-misc) + +;;;###autoload +(define-minor-mode pdf-misc-size-indication-minor-mode + "Provide a working size indication in the mode-line." + :group 'pdf-misc + (pdf-util-assert-pdf-buffer) + (cond + (pdf-misc-size-indication-minor-mode + (unless (assq 'pdf-misc-size-indication-minor-mode + mode-line-position) + (setq mode-line-position + `((pdf-misc-size-indication-minor-mode + (:eval (pdf-misc-size-indication))) + ,@mode-line-position)))) + (t + (setq mode-line-position + (cl-remove 'pdf-misc-size-indication-minor-mode + mode-line-position :key 'car-safe))))) + +(defun pdf-misc-size-indication () + "Return size indication string for the mode-line." + (let ((top (= (window-vscroll nil t) 0)) + (bot (>= (+ (- (nth 3 (window-inside-pixel-edges)) + (nth 1 (window-inside-pixel-edges))) + (window-vscroll nil t)) + (cdr (pdf-view-image-size t))))) + (cond + ((and top bot) " All") + (top " Top") + (bot " Bot") + (t (format + " %d%%%%" + (ceiling + (* 100 (/ (float (window-vscroll nil t)) + (cdr (pdf-view-image-size t)))))))))) + +(defvar pdf-misc-menu-bar-minor-mode-map (make-sparse-keymap) + "The keymap used in `pdf-misc-menu-bar-minor-mode'.") + +(easy-menu-define nil pdf-misc-menu-bar-minor-mode-map + "Menu for PDF Tools." + `("PDF Tools" + ["Go Backward" pdf-history-backward + :visible (bound-and-true-p pdf-history-minor-mode) + :active (and (bound-and-true-p pdf-history-minor-mode) + (not (pdf-history-end-of-history-p)))] + ["Go Forward" pdf-history-forward + :visible (bound-and-true-p pdf-history-minor-mode) + :active (not (pdf-history-end-of-history-p))] + ["--" nil + :visible (derived-mode-p 'pdf-virtual-view-mode)] + ["Next file" pdf-virtual-buffer-forward-file + :visible (derived-mode-p 'pdf-virtual-view-mode) + :active (pdf-virtual-document-next-file + (pdf-view-current-page))] + ["Previous file" pdf-virtual-buffer-backward-file + :visible (derived-mode-p 'pdf-virtual-view-mode) + :active (not (eq 1 (pdf-view-current-page)))] + ["--" nil + :visible (bound-and-true-p pdf-history-minor-mode)] + ["Add text annotation" pdf-annot-mouse-add-text-annotation + :visible (bound-and-true-p pdf-annot-minor-mode) + :keys "\\[pdf-annot-add-text-annotation]"] + ("Add markup annotation" + :active (pdf-view-active-region-p) + :visible (and (bound-and-true-p pdf-annot-minor-mode) + (pdf-info-markup-annotations-p)) + ["highlight" pdf-annot-add-highlight-markup-annotation] + ["squiggly" pdf-annot-add-squiggly-markup-annotation] + ["underline" pdf-annot-add-underline-markup-annotation] + ["strikeout" pdf-annot-add-strikeout-markup-annotation]) + ["--" nil :visible (bound-and-true-p pdf-annot-minor-mode)] + ["Display Annotations" pdf-annot-list-annotations + :help "List all annotations" + :visible (bound-and-true-p pdf-annot-minor-mode)] + ["Display Attachments" pdf-annot-attachment-dired + :help "Display attachments in a dired buffer" + :visible (featurep 'pdf-annot)] + ["Display Metadata" pdf-misc-display-metadata + :help "Display information about the document" + :visible (featurep 'pdf-misc)] + ["Display Outline" pdf-outline + :help "Display documents outline" + :visible (featurep 'pdf-outline)] + "--" + ("Render Options" + ["Printed Mode" (lambda () + (interactive) + (pdf-view-printer-minor-mode 'toggle)) + :style toggle + :selected pdf-view-printer-minor-mode + :help "Display the PDF as it would be printed."] + ["Midnight Mode" (lambda () + (interactive) + (pdf-view-midnight-minor-mode 'toggle)) + :style toggle + :selected pdf-view-midnight-minor-mode + :help "Apply a color-filter appropriate for past midnight reading."]) + "--" + ["Copy region" pdf-view-kill-ring-save + :keys "\\[kill-ring-save]" + :active (pdf-view-active-region-p)] + "--" + ["Isearch document" isearch-forward + :visible (bound-and-true-p pdf-isearch-minor-mode)] + ["Occur document" pdf-occur + :visible (featurep 'pdf-occur)] + "--" + ["Locate TeX source" pdf-sync-backward-search-mouse + :visible (and (featurep 'pdf-sync) + (equal last-command-event + last-nonmenu-event))] + ["--" nil :visible (and (featurep 'pdf-sync) + (equal last-command-event + last-nonmenu-event))] + ["Print" pdf-misc-print-document + :active (and (pdf-view-buffer-file-name) + (file-readable-p (pdf-view-buffer-file-name)))] + ["Create image" pdf-view-extract-region-image + :help "Create an image of the page or the selected region(s)."] + ["Create virtual PDF" pdf-virtual-buffer-create + :help "Create a PDF containing all documents in this directory." + :visible (bound-and-true-p pdf-virtual-global-minor-mode)] + "--" + ["Revert buffer" pdf-view-revert-buffer + :visible (pdf-info-writable-annotations-p)] + "--" + ["Customize" pdf-tools-customize])) + +;;;###autoload +(define-minor-mode pdf-misc-menu-bar-minor-mode + "Display a PDF Tools menu in the menu-bar." + :group 'pdf-misc + (pdf-util-assert-pdf-buffer)) + +(defvar pdf-misc-context-menu-minor-mode-map + (let ((kmap (make-sparse-keymap))) + (define-key kmap [down-mouse-3] 'pdf-misc-popup-context-menu) + kmap)) + +;;;###autoload +(define-minor-mode pdf-misc-context-menu-minor-mode + "Provide a right-click context menu in PDF buffers. + +\\{pdf-misc-context-menu-minor-mode-map}" + :group 'pdf-misc + (pdf-util-assert-pdf-buffer)) + +(defun pdf-misc-popup-context-menu (event) + "Popup a context menu at position determined by EVENT." + (interactive "@e") + (popup-menu + (cons 'keymap + (cddr (or (lookup-key pdf-misc-menu-bar-minor-mode-map + [menu-bar PDF\ Tools]) + (lookup-key pdf-misc-menu-bar-minor-mode-map + [menu-bar pdf\ tools])))))) + +(defun pdf-misc-display-metadata () + "Display all available metadata in a separate buffer." + (interactive) + (pdf-util-assert-pdf-buffer) + (let* ((buffer (current-buffer)) + (md (pdf-info-metadata))) + (with-current-buffer (get-buffer-create "*PDF-Metadata*") + (let* ((inhibit-read-only t) + (pad (apply' max (mapcar (lambda (d) + (length (symbol-name (car d)))) + md))) + (fmt (format "%%%ds:%%s\n" pad)) + window) + (erase-buffer) + (setq header-line-format (buffer-name buffer) + buffer-read-only t) + (font-lock-mode 1) + (font-lock-add-keywords nil + '(("^ *\\(\\(?:\\w\\|-\\)+\\):" + (1 font-lock-keyword-face)))) + (dolist (d md) + (let ((key (car d)) + (val (cdr d))) + (cl-case key + (keywords + (setq val (mapconcat 'identity val ", ")))) + (let ((beg (+ (length (symbol-name key)) (point) 1)) + (fill-prefix + (make-string (1+ pad) ?\s))) + (insert (format fmt key val)) + (fill-region beg (point) ))))) + (goto-char 1) + (display-buffer (current-buffer))) + md)) + +(defgroup pdf-misc nil + "Miscellaneous options for PDF documents." + :group 'pdf-tools) + +(define-obsolete-variable-alias 'pdf-misc-print-programm + 'pdf-misc-print-program-executable "1.0") +(defcustom pdf-misc-print-program-executable nil + "The program used for printing. + +It is called with one argument, the PDF file." + :group 'pdf-misc + :type 'file) + +(define-obsolete-variable-alias 'pdf-misc-print-programm-args + 'pdf-misc-print-program-args "1.0") +(defcustom pdf-misc-print-program-args nil + "List of additional arguments passed to `pdf-misc-print-program'." + :group 'pdf-misc + :type '(repeat string)) + +(define-obsolete-function-alias 'pdf-misc-print-programm + 'pdf-misc-print-program "1.0") +(defun pdf-misc-print-program (&optional interactive-p) + "Return the program used to print PDFs (if the executable is installed). + +If INTERACTIVE-P is non-nil, ask the user for which program to +use when printing the PDF. Optionally, save the choice" + (or (and pdf-misc-print-program-executable + (executable-find pdf-misc-print-program-executable)) + (when interactive-p + (let* ((default (car (delq nil (mapcar + 'executable-find + '("gtklp" "xpp" "gpr"))))) + buffer-file-name + (program + (expand-file-name + (read-file-name + "Print with: " default nil t nil 'file-executable-p)))) + (when (and program + (executable-find program)) + (when (y-or-n-p "Save choice using customize? ") + (customize-save-variable + 'pdf-misc-print-program-executable program)) + (setq pdf-misc-print-program-executable program)))))) + +(defun pdf-misc-print-document (filename &optional interactive-p) + (interactive + (list (pdf-view-buffer-file-name) t)) + (cl-check-type filename (and string file-readable)) + (let ((program (pdf-misc-print-program interactive-p)) + (args (append pdf-misc-print-program-args (list filename)))) + (unless program + (error "No print program available")) + (apply #'start-process "printing" nil program args) + (message "Print job started: %s %s" + program (mapconcat #'identity args " ")))) + + +(provide 'pdf-misc) + +;;; pdf-misc.el ends here diff --git a/elpa/pdf-tools-20211110.513/pdf-misc.elc b/elpa/pdf-tools-20211110.513/pdf-misc.elc Binary files differ. diff --git a/elpa/pdf-tools-20211110.513/pdf-occur.el b/elpa/pdf-tools-20211110.513/pdf-occur.el @@ -0,0 +1,826 @@ +;;; pdf-occur.el --- Display matching lines of PDF documents. -*- lexical-binding: t -*- + +;; Copyright (C) 2013, 2014 Andreas Politz + +;; Author: Andreas Politz <politza@fh-trier.de> +;; Keywords: files, multimedia + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: +;; + +(require 'pdf-tools) +(require 'pdf-view) +(require 'pdf-util) +(require 'pdf-info) +(require 'pdf-isearch) +(require 'tablist) +(require 'ibuf-ext) +(require 'dired) +(require 'let-alist) + +;;; Code: + + + + +;; * ================================================================== * +;; * Custom & Variables +;; * ================================================================== * + +(defgroup pdf-occur nil + "Display matching lines of PDF documents." + :group 'pdf-tools) + +(defface pdf-occur-document-face + '((default (:inherit font-lock-string-face))) + "Face used to highlight documents in the list buffer." + :group 'pdf-occur) + +(defface pdf-occur-page-face + '((default (:inherit font-lock-type-face))) + "Face used to highlight page numbers in the list buffer." + :group 'pdf-occur) + +(defcustom pdf-occur-search-batch-size 16 + "Maximum number of pages searched in one query. + +Lower numbers will make Emacs more responsive when searching at +the cost of slightly increased search time." + :group 'pdf-occur + :type 'integer) + +(defcustom pdf-occur-prefer-string-search nil + "If non-nil, reverse the meaning of the regexp-p prefix-arg." + :group 'pdf-occur + :type 'boolean) + +(defvar pdf-occur-history nil + "The history variable for search strings.") + +(defvar pdf-occur-search-pages-left nil + "The total number of pages left to search.") + +(defvar pdf-occur-search-documents nil + "The list of searched documents. + +Each element should be either the filename of a PDF document or a +cons \(FILENAME . PAGES\), where PAGES is the list of pages to +search. See `pdf-info-normalize-page-range' for it's format.") + +(defvar pdf-occur-number-of-matches 0 + "The number of matches in all searched documents.") + +(defvar pdf-occur-search-string nil + "The currently used search string, resp. regexp.") + +(defvar pdf-occur-search-regexp-p nil + "Non-nil, if searching for a regexp.") + +(defvar pdf-occur-buffer-mode-map + (let ((kmap (make-sparse-keymap))) + (set-keymap-parent kmap tablist-mode-map) + (define-key kmap (kbd "RET") 'pdf-occur-goto-occurrence) + (define-key kmap (kbd "C-o") 'pdf-occur-view-occurrence) + (define-key kmap (kbd "SPC") 'pdf-occur-view-occurrence) + (define-key kmap (kbd "C-c C-f") 'next-error-follow-minor-mode) + (define-key kmap (kbd "g") 'pdf-occur-revert-buffer-with-args) + (define-key kmap (kbd "K") 'pdf-occur-abort-search) + (define-key kmap (kbd "D") 'pdf-occur-tablist-do-delete) + (define-key kmap (kbd "x") 'pdf-occur-tablist-do-flagged-delete) + (define-key kmap (kbd "A") 'pdf-occur-tablist-gather-documents) + kmap) + "The keymap used for `pdf-occur-buffer-mode'.") + + +;; * ================================================================== * +;; * High level functions +;; * ================================================================== * + +(define-derived-mode pdf-occur-buffer-mode tablist-mode "PDFOccur" + "Major mode for output from `pdf-occur`. \\<pdf-occur-buffer-mode-map> + +Some useful keys are: + +\\[pdf-occur-abort-search] - Abort the search. +\\[pdf-occur-revert-buffer-with-args] - Restart the search. +\\[universal-argument] \\[pdf-occur-revert-buffer-with-args] - Restart search with different regexp. +\\[universal-argument] \\[universal-argument] \\[pdf-occur-revert-buffer-with-args] - Same, but do a plain string search. + +\\[tablist-push-regexp-filter] - Filter matches by regexp on current or prefix-th column. +\\[tablist-pop-filter] - Remove last added filter. + +\\[pdf-occur-tablist-do-delete] - Remove the current file from the search. +\\[pdf-occur-tablist-gather-documents] - Include marked files from displayed `dired'/`ibuffer' and + `pdf-view-mode' buffers in the search. + +\\{pdf-occur-buffer-mode-map}" + (setq-local case-fold-search case-fold-search) + (setq-local next-error-function 'pdf-occur-next-error) + (setq-local revert-buffer-function + 'pdf-occur-revert-buffer) + (setq next-error-last-buffer (current-buffer)) + (setq-local tabulated-list-sort-key nil) + (setq-local tabulated-list-use-header-line t) + (setq-local tablist-operations-function + (lambda (op &rest _) + (cl-case op + (supported-operations '(find-entry)) + (find-entry + (let ((display-buffer-overriding-action + '(display-buffer-same-window))) + (pdf-occur-goto-occurrence))))))) + +;;;###autoload +(defun pdf-occur (string &optional regexp-p) + "List lines matching STRING or PCRE. + +Interactively search for a regexp. Unless a prefix arg was given, +in which case this functions performs a string search. + +If `pdf-occur-prefer-string-search' is non-nil, the meaning of +the prefix-arg is inverted." + (interactive + (progn + (pdf-util-assert-pdf-buffer) + (list + (pdf-occur-read-string + (pdf-occur-want-regexp-search-p)) + (pdf-occur-want-regexp-search-p)))) + (pdf-util-assert-pdf-buffer) + (pdf-occur-search (list (current-buffer)) string regexp-p)) + +(defvar ibuffer-filtering-qualifiers) +;;;###autoload +(defun pdf-occur-multi-command () + "Perform `pdf-occur' on multiple buffer. + +For a programmatic search of multiple documents see +`pdf-occur-search'." + (interactive) + (ibuffer) + (with-current-buffer "*Ibuffer*" + (pdf-occur-ibuffer-minor-mode) + (unless (member '(derived-mode . pdf-view-mode) + ibuffer-filtering-qualifiers) + (ibuffer-filter-by-derived-mode 'pdf-view-mode)) + (message + "%s" + (substitute-command-keys + "Mark a bunch of PDF buffers and type \\[pdf-occur-ibuffer-do-occur]")) + (sit-for 3))) + +(defun pdf-occur-revert-buffer (&rest _) + "Restart the search." + (pdf-occur-assert-occur-buffer-p) + (unless pdf-occur-search-documents + (error "No documents to search")) + (unless pdf-occur-search-string + (error "Nothing to search for")) + (let* ((2-columns-p (= 1 (length pdf-occur-search-documents))) + (filename-width + (min 24 + (apply 'max + (mapcar 'length + (mapcar 'pdf-occur-abbrev-document + (mapcar 'car pdf-occur-search-documents)))))) + (page-sorter (tablist-generate-sorter + (if 2-columns-p 0 1) + '< + 'string-to-number))) + (setq tabulated-list-format + (if 2-columns-p + `[("Page" 4 ,page-sorter :right-align t) + ("Line" 0 t)] + `[("Document" ,filename-width t) + ("Page" 4 ,page-sorter :right-align t) + ("Line" 0 t)]) + tabulated-list-entries nil)) + (tabulated-list-revert) + (pdf-occur-start-search + pdf-occur-search-documents + pdf-occur-search-string + pdf-occur-search-regexp-p) + (pdf-occur-update-header-line) + (setq mode-line-process + '(:propertize ":run" face compilation-mode-line-run))) + +(defun pdf-occur-revert-buffer-with-args (string &optional regexp-p documents) + "Restart the search with modified arguments. + +Interactively just restart the search, unless a prefix was given. +In this case read a new search string. With `C-u C-u' as prefix +additionally invert the current state of +`pdf-occur-search-regexp-p'." + (interactive + (progn + (pdf-occur-assert-occur-buffer-p) + (cond + (current-prefix-arg + (let ((regexp-p + (if (equal current-prefix-arg '(16)) + (not pdf-occur-search-regexp-p) + pdf-occur-search-regexp-p))) + (list + (pdf-occur-read-string regexp-p) + regexp-p))) + (t + (list pdf-occur-search-string + pdf-occur-search-regexp-p))))) + (setq pdf-occur-search-string string + pdf-occur-search-regexp-p regexp-p) + (when documents + (setq pdf-occur-search-documents + (pdf-occur-normalize-documents documents))) + (pdf-occur-revert-buffer)) + +(defun pdf-occur-abort-search () + "Abort the current search. + +This immediately kills the search process." + (interactive) + (unless (pdf-occur-search-in-progress-p) + (user-error "No search in progress")) + (pdf-info-kill-local-server) + (pdf-occur-search-finished t)) + + +;; * ================================================================== * +;; * Finding occurrences +;; * ================================================================== * + + +(defun pdf-occur-goto-occurrence (&optional no-select-window-p) + "Go to the occurrence at point. + +If EVENT is nil, use occurrence at current line. Select the +PDF's window, unless NO-SELECT-WINDOW-P is non-nil. + +FIXME: EVENT not used at the moment." + (interactive) + (let ((item (tabulated-list-get-id))) + (when item + (let* ((doc (plist-get item :document)) + (page (plist-get item :page)) + (match (plist-get item :match-edges)) + (buffer (if (bufferp doc) + doc + (or (find-buffer-visiting doc) + (find-file-noselect doc)))) + window) + (if no-select-window-p + (setq window (display-buffer buffer)) + (pop-to-buffer buffer) + (setq window (selected-window))) + (with-selected-window window + (when page + (pdf-view-goto-page page)) + ;; Abuse isearch. + (when match + (let ((pixel-match + (pdf-util-scale-relative-to-pixel match)) + (pdf-isearch-batch-mode t)) + (pdf-isearch-hl-matches pixel-match nil t) + (pdf-isearch-focus-match-batch pixel-match)))))))) + +(defun pdf-occur-view-occurrence (&optional _event) + "View the occurrence at EVENT. + +If EVENT is nil, use occurrence at current line." + (interactive (list last-nonmenu-event)) + (pdf-occur-goto-occurrence t)) + +(defun pdf-occur-next-error (&optional arg reset) + "Move to the Nth (default 1) next match in an PDF Occur mode buffer. +Compatibility function for \\[next-error] invocations." + (interactive "p") + ;; we need to run pdf-occur-find-match from within the Occur buffer + (with-current-buffer + ;; Choose the buffer and make it current. + (if (next-error-buffer-p (current-buffer)) + (current-buffer) + (next-error-find-buffer + nil nil + (lambda () + (eq major-mode 'pdf-occur-buffer-mode)))) + (when (bobp) + (setq reset t)) + (if reset + (goto-char (point-min)) + (beginning-of-line)) + (when (/= arg 0) + (when (eobp) + (forward-line -1)) + (when reset + (cl-decf arg)) + (let ((line (line-number-at-pos)) + (limit (line-number-at-pos + (if (>= arg 0) + (1- (point-max)) + (point-min))))) + (when (= line limit) + (error "No more matches")) + (forward-line + (if (>= arg 0) + (min arg (- limit line)) + (max arg (- limit line)))))) + ;; In case the *Occur* buffer is visible in a nonselected window. + (tablist-move-to-major-column) + (let ((win (get-buffer-window (current-buffer) t))) + (if win (set-window-point win (point)))) + (pdf-occur-goto-occurrence))) + + +;; * ================================================================== * +;; * Integration with other modes +;; * ================================================================== * + +;;;###autoload +(define-minor-mode pdf-occur-global-minor-mode + "Enable integration of Pdf Occur with other modes. + +This global minor mode enables (or disables) +`pdf-occur-ibuffer-minor-mode' and `pdf-occur-dired-minor-mode' +in all current and future ibuffer/dired buffer." + :global t + :group 'pdf-occur + (let ((arg (if pdf-occur-global-minor-mode 1 -1))) + (dolist (buf (buffer-list)) + (with-current-buffer buf + (cond + ((derived-mode-p 'dired-mode) + (pdf-occur-dired-minor-mode arg)) + ((derived-mode-p 'ibuffer-mode) + (pdf-occur-ibuffer-minor-mode arg))))) + (cond + (pdf-occur-global-minor-mode + (add-hook 'dired-mode-hook 'pdf-occur-dired-minor-mode) + (add-hook 'ibuffer-mode-hook 'pdf-occur-ibuffer-minor-mode)) + (t + (remove-hook 'dired-mode-hook 'pdf-occur-dired-minor-mode) + (remove-hook 'ibuffer-mode-hook 'pdf-occur-ibuffer-minor-mode))))) + +(defvar pdf-occur-ibuffer-minor-mode-map + (let ((map (make-sparse-keymap))) + (define-key map [remap ibuffer-do-occur] 'pdf-occur-ibuffer-do-occur) + map) + "Keymap used in `pdf-occur-ibuffer-minor-mode'.") + +;;;###autoload +(define-minor-mode pdf-occur-ibuffer-minor-mode + "Hack into ibuffer's do-occur binding. + +This mode remaps `ibuffer-do-occur' to +`pdf-occur-ibuffer-do-occur', which will start the PDF Tools +version of `occur', if all marked buffer's are in `pdf-view-mode' +and otherwise fallback to `ibuffer-do-occur'." + :group 'pdf-occur) + +(defun pdf-occur-ibuffer-do-occur (&optional regexp-p) + "Uses `pdf-occur-search', if appropriate. + +I.e. all marked buffers are in PDFView mode." + (interactive + (list (pdf-occur-want-regexp-search-p))) + (let* ((buffer (or (ibuffer-get-marked-buffers) + (and (ibuffer-current-buffer) + (list (ibuffer-current-buffer))))) + (pdf-only-p (cl-every + (lambda (buf) + (with-current-buffer buf + (derived-mode-p 'pdf-view-mode))) + buffer))) + (if (not pdf-only-p) + (call-interactively 'ibuffer-do-occur) + (let ((regexp (pdf-occur-read-string regexp-p))) + (pdf-occur-search buffer regexp regexp-p))))) + +(defvar pdf-occur-dired-minor-mode-map + (let ((map (make-sparse-keymap))) + (define-key map [remap dired-do-search] 'pdf-occur-dired-do-search) + map) + "Keymap used in `pdf-occur-dired-minor-mode'.") + +;;;###autoload +(define-minor-mode pdf-occur-dired-minor-mode + "Hack into dired's `dired-do-search' binding. + +This mode remaps `dired-do-search' to +`pdf-occur-dired-do-search', which will start the PDF Tools +version of `occur', if all marked buffer's are in `pdf-view-mode' +and otherwise fallback to `dired-do-search'." + :group 'pdf-occur) + +(defun pdf-occur-dired-do-search () + "Uses `pdf-occur-search', if appropriate. + +I.e. all marked files look like PDF documents." + (interactive) + (let ((files (dired-get-marked-files))) + (if (not (cl-every (lambda (file) + (string-match-p + (car pdf-tools-auto-mode-alist-entry) + file)) + files)) + (call-interactively 'dired-do-search) + (let* ((regex-p (pdf-occur-want-regexp-search-p)) + (regexp (pdf-occur-read-string regex-p))) + (pdf-occur-search files regexp regex-p))))) + + + +;; * ================================================================== * +;; * Search engine +;; * ================================================================== * + + +(defun pdf-occur-search (documents string &optional regexp-p) + "Search DOCUMENTS for STRING. + +DOCUMENTS should be a list of buffers (objects, not names), +filenames or conses \(BUFFER-OR-FILENAME . PAGES\), where PAGES +determines the scope of the search of the respective document. +See `pdf-info-normalize-page-range' for it's format. + +STRING is either the string to search for or, if REGEXP-P is +non-nil, a Perl compatible regular expression (PCRE). + +Display the occur buffer and start the search asynchronously. + +Returns the window where the buffer is displayed." + + (unless documents + (error "No documents to search")) + (when (or (null string) (= (length string) 0)) + (error "Not searching for the empty string")) + (with-current-buffer (get-buffer-create "*PDF-Occur*") + (pdf-occur-buffer-mode) + (setq-local pdf-occur-search-documents + (pdf-occur-normalize-documents documents)) + (setq-local pdf-occur-search-string string) + (setq-local pdf-occur-search-regexp-p regexp-p) + (setq-local pdf-occur-search-pages-left 0) + (setq-local pdf-occur-number-of-matches 0) + (pdf-occur-revert-buffer) + (display-buffer + (current-buffer)))) + +(defadvice tabulated-list-init-header (after update-header activate) + "We want our own headers, thank you." + (when (derived-mode-p 'pdf-occur-buffer-mode) + (save-current-buffer + (with-no-warnings (pdf-occur-update-header-line))))) + +(defun pdf-occur-create-entry (filename page &optional match) + "Create a `tabulated-list-entries' entry for a search result. + +If match is nil, create a fake entry for documents w/o any +matches linked with PAGE." + (let* ((text (or (car match) "[No matches]")) + (edges (cdr match)) + (displayed-text + (if match + (replace-regexp-in-string "\n" "\\n" text t t) + (propertize text 'face 'font-lock-warning-face))) + (displayed-page + (if match + (propertize (format "%d" page) + 'face 'pdf-occur-page-face) + "")) + (displayed-document + (propertize + (pdf-occur-abbrev-document filename) + 'face 'pdf-occur-document-face)) + (id `(:document ,filename + :page ,page + :match-text ,(if match text) + :match-edges ,(if match edges)))) + (list id + (if (= (length pdf-occur-search-documents) 1) + (vector displayed-page displayed-text) + (vector displayed-document + displayed-page + displayed-text))))) + +(defun pdf-occur-update-header-line () + (pdf-occur-assert-occur-buffer-p) + (save-current-buffer + ;;force-mode-line-update seems to sometimes spuriously change the + ;;current buffer. + (setq header-line-format + `(:eval (concat + (if (= (length pdf-occur-search-documents) 1) + (format "%d match%s in document `%s'" + pdf-occur-number-of-matches + (if (/= 1 pdf-occur-number-of-matches) "es" "") + (pdf-occur-abbrev-document + (caar pdf-occur-search-documents))) + (format "%d match%s in %d documents" + pdf-occur-number-of-matches + (if (/= 1 pdf-occur-number-of-matches) "es" "") + (length pdf-occur-search-documents))) + (if (pdf-occur-search-in-progress-p) + (propertize + (concat " [" + (if (numberp pdf-occur-search-pages-left) + (format "%d pages left" + pdf-occur-search-pages-left) + "Searching") + "]") + 'face 'compilation-mode-line-run))))) + (force-mode-line-update))) + +(defun pdf-occur-search-finished (&optional abort-p) + (setq pdf-occur-search-pages-left 0) + (setq mode-line-process + (if abort-p + '(:propertize + ":aborted" face compilation-mode-line-fail) + '(:propertize + ":exit" face compilation-mode-line-exit))) + (let ((unmatched + (mapcar (lambda (doc) + (pdf-occur-create-entry doc 1)) + (cl-set-difference + (mapcar 'car + pdf-occur-search-documents) + (mapcar (lambda (elt) + (plist-get (car elt) :document)) + tabulated-list-entries) + :test 'equal)))) + (when (and unmatched + (> (length pdf-occur-search-documents) 1)) + (pdf-occur-insert-entries unmatched))) + (tablist-apply-filter) + (pdf-occur-update-header-line) + (pdf-isearch-message + (if abort-p + "Search aborted." + (format "Occur search finished with %d matches" + pdf-occur-number-of-matches)))) + +(defun pdf-occur-add-matches (filename matches) + (pdf-occur-assert-occur-buffer-p) + (when matches + (let (entries) + (dolist (match matches) + (let-alist match + (push (pdf-occur-create-entry filename .page (cons .text .edges)) + entries))) + (setq entries (nreverse entries)) + (pdf-occur-insert-entries entries)))) + +(defun pdf-occur-insert-entries (entries) + "Insert tabulated-list ENTRIES at the end." + (pdf-occur-assert-occur-buffer-p) + (let ((inhibit-read-only t) + (end-of-buffer (and (eobp) (not (bobp))))) + (save-excursion + (goto-char (point-max)) + (dolist (elt entries) + (apply tabulated-list-printer elt)) + (set-buffer-modified-p nil)) + (when end-of-buffer + (dolist (win (get-buffer-window-list)) + (set-window-point win (point-max)))) + (setq tabulated-list-entries + (append tabulated-list-entries + entries)))) + +(defun pdf-occur-search-in-progress-p () + (and (numberp pdf-occur-search-pages-left) + (> pdf-occur-search-pages-left 0))) + +(defun pdf-occur-start-search (documents string + &optional regexp-p) + (pdf-occur-assert-occur-buffer-p) + (pdf-info-make-local-server nil t) + (let ((batches (pdf-occur-create-batches + documents (or pdf-occur-search-batch-size 1)))) + (pdf-info-local-batch-query + (lambda (document pages) + (if regexp-p + (pdf-info-search-regexp string pages nil document) + (pdf-info-search-string string pages document))) + (lambda (status response document pages) + (if status + (error "%s" response) + (when (numberp pdf-occur-search-pages-left) + (cl-decf pdf-occur-search-pages-left + (1+ (- (cdr pages) (car pages))))) + (when (cl-member document pdf-occur-search-documents + :key 'car + :test 'equal) + (cl-incf pdf-occur-number-of-matches + (length response)) + (pdf-occur-add-matches document response) + (pdf-occur-update-header-line)))) + (lambda (status buffer) + (when (buffer-live-p buffer) + (with-current-buffer buffer + (pdf-occur-search-finished (eq status 'killed))))) + batches) + (setq pdf-occur-number-of-matches 0) + (setq pdf-occur-search-pages-left + (apply '+ (mapcar (lambda (elt) + (1+ (- (cdr (nth 1 elt)) + (car (nth 1 elt))))) + batches))))) + + + +;; * ================================================================== * +;; * Editing searched documents +;; * ================================================================== * + +(defun pdf-occur-tablist-do-delete (&optional arg) + "Delete ARG documents from the search list." + (interactive "P") + (when (pdf-occur-search-in-progress-p) + (user-error "Can't delete while a search is in progress.")) + (let* ((items (tablist-get-marked-items arg)) + (documents (cl-remove-duplicates + (mapcar (lambda (entry) + (plist-get (car entry) :document)) + items) + :test 'equal))) + (unless documents + (error "No documents selected")) + (when (tablist-yes-or-no-p + 'Stop\ searching + nil (mapcar (lambda (d) (cons nil (vector d))) + documents)) + (setq pdf-occur-search-documents + (cl-remove-if (lambda (elt) + (member (car elt) documents)) + pdf-occur-search-documents) + tabulated-list-entries + (cl-remove-if (lambda (elt) + (when (member (plist-get (car elt) :document) + documents) + (when (plist-get (car elt) :match-edges) + (cl-decf pdf-occur-number-of-matches)) + t)) + tabulated-list-entries)) + (tablist-revert) + (pdf-occur-update-header-line) + (tablist-move-to-major-column)))) + +(defun pdf-occur-tablist-do-flagged-delete (&optional interactive) + "Stop searching all documents marked with a D." + (interactive "p") + (let* ((tablist-marker-char ?D)) + (if (save-excursion + (goto-char (point-min)) + (re-search-forward (tablist-marker-regexp) nil t)) + (pdf-occur-tablist-do-delete) + (or (not interactive) + (message "(No deletions requested)"))))) + +(defun pdf-occur-tablist-gather-documents () + "Gather marked documents in windows. + +Examine all dired/ibuffer windows and offer to put marked files +in the search list." + (interactive) + (let ((searched (mapcar 'car pdf-occur-search-documents)) + files) + (dolist (win (window-list)) + (with-selected-window win + (cond + ((derived-mode-p 'dired-mode) + (let ((marked (dired-get-marked-files nil nil nil t))) + (when (> (length marked) 1) + (when (eq t (car marked)) + (setq marked (cdr marked))) + (setq files + (append files marked nil))))) + ((derived-mode-p 'ibuffer-mode) + (dolist (fname (mapcar 'buffer-file-name + (ibuffer-get-marked-buffers))) + (when fname + (push fname files)))) + ((and (derived-mode-p 'pdf-view-mode) + (buffer-file-name)) + (push (buffer-file-name) files))))) + + (setq files + (cl-sort ;Looks funny. + (cl-set-difference + (cl-remove-duplicates + (cl-remove-if-not + (lambda (file) (string-match-p + (car pdf-tools-auto-mode-alist-entry) + file)) + files) + :test 'file-equal-p) + searched + :test 'file-equal-p) + 'string-lessp)) + (if (null files) + (message "No marked, new PDF files found in windows") + (when (tablist-yes-or-no-p + 'add nil (mapcar (lambda (file) + (cons nil (vector file))) + (cl-sort files 'string-lessp))) + (setq pdf-occur-search-documents + (append pdf-occur-search-documents + (pdf-occur-normalize-documents files))) + (message "Added %d file%s to the list of searched documents%s" + (length files) + (dired-plural-s (length files)) + (substitute-command-keys + " - Hit \\[pdf-occur-revert-buffer-with-args]")))))) + + +;; * ================================================================== * +;; * Utilities +;; * ================================================================== * + +(defun pdf-occur-read-string (&optional regexp-p) + (read-string + (concat + (format "List lines %s" + (if regexp-p "matching PCRE" "containing string")) + (if pdf-occur-search-string + (format " (default %s)" pdf-occur-search-string)) + ": ") + nil 'pdf-occur-history pdf-occur-search-string)) + +(defun pdf-occur-assert-occur-buffer-p () + (unless (derived-mode-p 'pdf-occur-buffer-mode) + (error "Not in PDF occur buffer"))) + +(defun pdf-occur-want-regexp-search-p () + (or (and current-prefix-arg + pdf-occur-prefer-string-search) + (and (null current-prefix-arg) + (not pdf-occur-prefer-string-search)))) + +;; FIXME: This will be confusing when searching documents with the +;; same base file-name. +(defun pdf-occur-abbrev-document (file-or-buffer) + (if (bufferp file-or-buffer) + (buffer-name file-or-buffer) + (let ((abbrev (file-name-nondirectory file-or-buffer))) + (if (> (length abbrev) 0) + abbrev + file-or-buffer)))) + +(defun pdf-occur-create-batches (documents batch-size) + (let (queries) + (dolist (d documents) + (let* ((file-or-buffer (car d)) + (pages (pdf-info-normalize-page-range (cdr d))) + (first (car pages)) + (last (if (eq (cdr pages) 0) + (pdf-info-number-of-pages file-or-buffer) + (cdr pages))) + (npages (1+ (- last first))) + (nbatches (ceiling + (/ (float npages) batch-size)))) + (dotimes (i nbatches) + (push + (list file-or-buffer + (cons (+ first (* i batch-size)) + (min last (+ first (1- (* (1+ i) batch-size)))))) + queries)))) + (nreverse queries))) + +(defun pdf-occur-normalize-documents (documents) + "Normalize list of documents. + +Replaces buffers with their associated filenames \(if +applicable\) and ensures that every element looks like +\(FILENAME-OR-BUFFER . PAGES\)." + (cl-sort (mapcar (lambda (doc) + (unless (consp doc) + (setq doc (cons doc nil))) + (when (and (bufferp (car doc)) + (buffer-file-name (car doc))) + (setq doc (cons (buffer-file-name (car doc)) + (cdr doc)))) + (if (stringp (car doc)) + (cons (expand-file-name (car doc)) (cdr doc)) + doc)) + documents) + (lambda (a b) (string-lessp + (if (bufferp a) (buffer-name a) a) + (if (bufferp b) (buffer-name b) b))) + :key 'car)) + +(provide 'pdf-occur) + +;;; pdf-occur.el ends here diff --git a/elpa/pdf-tools-20211110.513/pdf-occur.elc b/elpa/pdf-tools-20211110.513/pdf-occur.elc Binary files differ. diff --git a/elpa/pdf-tools-20211110.513/pdf-outline.el b/elpa/pdf-tools-20211110.513/pdf-outline.el @@ -0,0 +1,606 @@ +;;; pdf-outline.el --- Outline for PDF buffer -*- lexical-binding: t -*- + +;; Copyright (C) 2013, 2014 Andreas Politz + +;; Author: Andreas Politz <politza@fh-trier.de> +;; Keywords: files, multimedia + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: +;; + +(require 'outline) +(require 'pdf-links) +(require 'pdf-view) +(require 'pdf-util) +(require 'cl-lib) +(require 'imenu) +(require 'let-alist) + +;;; Code: + +;; +;; User options +;; + +(defgroup pdf-outline nil + "Display a navigatable outline of a PDF document." + :group 'pdf-tools) + +(defcustom pdf-outline-buffer-indent 2 + "The level of indent in the Outline buffer." + :type 'integer + :group 'pdf-outline) + +(defcustom pdf-outline-enable-imenu t + "Whether `imenu' should be enabled in PDF documents." + :group 'pdf-outline + :type '(choice (const :tag "Yes" t) + (const :tag "No" nil))) + +(defcustom pdf-outline-imenu-keep-order t + "Whether `imenu' should be advised not to reorder the outline." + :group 'pdf-outline + :type '(choice (const :tag "Yes" t) + (const :tag "No" nil))) + +(defcustom pdf-outline-imenu-use-flat-menus nil + "Whether the constructed Imenu should be a list, rather than a tree." + :group 'pdf-outline + :type '(choice (const :tag "Yes" t) + (const :tag "No" nil))) + +(defcustom pdf-outline-display-buffer-action '(nil . nil) + "The display action used, when displaying the outline buffer." + :group 'pdf-outline + :type display-buffer--action-custom-type) + +(defcustom pdf-outline-display-labels nil + "Whether the outline should display labels instead of page numbers. + +Usually a page's label is it's displayed page number." + :group 'pdf-outline + :type 'boolean) + +(defcustom pdf-outline-fill-column fill-column + "The value of `fill-column' in pdf outline buffers. + +Set to nil to disable line wrapping." + :group 'pdf-outline + :type 'integer) + +(defvar pdf-outline-minor-mode-map + (let ((km (make-sparse-keymap))) + (define-key km (kbd "o") 'pdf-outline) + km) + "Keymap used for `pdf-outline-minor-mode'.") + +(defvar pdf-outline-buffer-mode-map + (let ((kmap (make-sparse-keymap))) + (dotimes (i 10) + (define-key kmap (vector (+ i ?0)) 'digit-argument)) + (define-key kmap "-" 'negative-argument) + (define-key kmap (kbd "p") 'previous-line) + (define-key kmap (kbd "n") 'next-line) + (define-key kmap (kbd "b") 'outline-backward-same-level) + (define-key kmap (kbd "d") 'hide-subtree) + (define-key kmap (kbd "a") 'show-all) + (define-key kmap (kbd "s") 'show-subtree) + (define-key kmap (kbd "f") 'outline-forward-same-level) + (define-key kmap (kbd "u") 'pdf-outline-up-heading) + (define-key kmap (kbd "Q") 'hide-sublevels) + (define-key kmap (kbd "<") 'beginning-of-buffer) + (define-key kmap (kbd ">") 'pdf-outline-end-of-buffer) + (define-key kmap (kbd "TAB") 'outline-toggle-children) + (define-key kmap (kbd "RET") 'pdf-outline-follow-link) + (define-key kmap (kbd "C-o") 'pdf-outline-display-link) + (define-key kmap (kbd "SPC") 'pdf-outline-display-link) + (define-key kmap [mouse-1] 'pdf-outline-mouse-display-link) + (define-key kmap (kbd "o") 'pdf-outline-select-pdf-window) + (define-key kmap (kbd ".") 'pdf-outline-move-to-current-page) + ;; (define-key kmap (kbd "Q") 'pdf-outline-quit) + (define-key kmap (kbd "C-c C-q") 'pdf-outline-quit-and-kill) + (define-key kmap (kbd "q") 'quit-window) + (define-key kmap (kbd "M-RET") 'pdf-outline-follow-link-and-quit) + (define-key kmap (kbd "C-c C-f") 'pdf-outline-follow-mode) + kmap) + "Keymap used in `pdf-outline-buffer-mode'.") + +;; +;; Internal Variables +;; + +(define-button-type 'pdf-outline + 'face nil + 'keymap nil) + +(defvar-local pdf-outline-pdf-window nil + "The PDF window corresponding to this outline buffer.") + +(defvar-local pdf-outline-pdf-document nil + "The PDF filename or buffer corresponding to this outline + buffer.") + +(defvar-local pdf-outline-follow-mode-last-link nil) + +;; +;; Functions +;; + +;;;###autoload +(define-minor-mode pdf-outline-minor-mode + "Display an outline of a PDF document. + +This provides a PDF's outline on the menu bar via imenu. +Additionally the same outline may be viewed in a designated +buffer. + +\\{pdf-outline-minor-mode-map}" + :group 'pdf-outline + (pdf-util-assert-pdf-buffer) + (cond + (pdf-outline-minor-mode + (when pdf-outline-enable-imenu + (pdf-outline-imenu-enable))) + (t + (when pdf-outline-enable-imenu + (pdf-outline-imenu-disable))))) + +(define-derived-mode pdf-outline-buffer-mode outline-mode "PDF Outline" + "View and traverse the outline of a PDF file. + +Press \\[pdf-outline-display-link] to display the PDF document, +\\[pdf-outline-select-pdf-window] to select it's window, +\\[pdf-outline-move-to-current-page] to move to the outline item +of the current page, \\[pdf-outline-follow-link] to goto the +corresponding page or \\[pdf-outline-follow-link-and-quit] to +additionally quit the Outline. + +\\[pdf-outline-follow-mode] enters a variant of +`next-error-follow-mode'. Most `outline-mode' commands are +rebound to their respective last character. + +\\{pdf-outline-buffer-mode-map}" + (setq-local outline-regexp "\\( *\\).") + (setq-local outline-level + (lambda nil (1+ (/ (length (match-string 1)) + pdf-outline-buffer-indent)))) + + (toggle-truncate-lines 1) + (setq buffer-read-only t) + (when (> (count-lines 1 (point-max)) + (* 1.5 (frame-height))) + (hide-sublevels 1)) + (message "%s" + (substitute-command-keys + (concat + "Try \\[pdf-outline-display-link], " + "\\[pdf-outline-select-pdf-window], " + "\\[pdf-outline-move-to-current-page] or " + "\\[pdf-outline-follow-link-and-quit]")))) + +(define-minor-mode pdf-outline-follow-mode + "Display links as point moves." + :group 'pdf-outline + (setq pdf-outline-follow-mode-last-link nil) + (cond + (pdf-outline-follow-mode + (add-hook 'post-command-hook 'pdf-outline-follow-mode-pch nil t)) + (t + (remove-hook 'post-command-hook 'pdf-outline-follow-mode-pch t)))) + +(defun pdf-outline-follow-mode-pch () + (let ((link (pdf-outline-link-at-pos (point)))) + (when (and link + (not (eq link pdf-outline-follow-mode-last-link))) + (setq pdf-outline-follow-mode-last-link link) + (pdf-outline-display-link (point))))) + +;;;###autoload +(defun pdf-outline (&optional buffer no-select-window-p) + "Display an PDF outline of BUFFER. + +BUFFER defaults to the current buffer. Select the outline +buffer, unless NO-SELECT-WINDOW-P is non-nil." + (interactive (list nil (or current-prefix-arg + (consp last-nonmenu-event)))) + (let ((win + (display-buffer + (pdf-outline-noselect buffer) + pdf-outline-display-buffer-action))) + (unless no-select-window-p + (select-window win)))) + +(defun pdf-outline-noselect (&optional buffer) + "Create an PDF outline of BUFFER, but don't display it." + (save-current-buffer + (and buffer (set-buffer buffer)) + (pdf-util-assert-pdf-buffer) + (let* ((pdf-buffer (current-buffer)) + (pdf-file (pdf-view-buffer-file-name)) + (pdf-window (and (eq pdf-buffer (window-buffer)) + (selected-window))) + (bname (pdf-outline-buffer-name)) + (buffer-exists-p (get-buffer bname)) + (buffer (get-buffer-create bname))) + (with-current-buffer buffer + (setq-local fill-column pdf-outline-fill-column) + (unless buffer-exists-p + (when (= 0 (save-excursion + (pdf-outline-insert-outline pdf-buffer))) + (kill-buffer buffer) + (error "PDF has no outline")) + (pdf-outline-buffer-mode)) + (set (make-local-variable 'other-window-scroll-buffer) + pdf-buffer) + (setq pdf-outline-pdf-window pdf-window + pdf-outline-pdf-document (or pdf-file pdf-buffer)) + (current-buffer))))) + +(defun pdf-outline-buffer-name (&optional pdf-buffer) + (unless pdf-buffer (setq pdf-buffer (current-buffer))) + (let ((buf (format "*Outline %s*" (buffer-name pdf-buffer)))) + ;; (when (buffer-live-p (get-buffer buf)) + ;; (kill-buffer buf)) + buf)) + +(defun pdf-outline-insert-outline (pdf-buffer) + (let ((labels (and pdf-outline-display-labels + (pdf-info-pagelabels pdf-buffer))) + (nitems 0)) + (dolist (item (pdf-info-outline pdf-buffer)) + (let-alist item + (when (eq .type 'goto-dest) + (insert-text-button + (concat + (make-string (* (1- .depth) pdf-outline-buffer-indent) ?\s) + .title + (if (> .page 0) + (format " (%s)" + (if labels + (nth (1- .page) labels) + .page)) + "(invalid)")) + 'type 'pdf-outline + 'help-echo (pdf-links-action-to-string item) + 'pdf-outline-link item) + (newline) + (cl-incf nitems)))) + nitems)) + +(defun pdf-outline-get-pdf-window (&optional if-visible-p) + (save-selected-window + (let* ((buffer (cond + ((buffer-live-p pdf-outline-pdf-document) + pdf-outline-pdf-document) + ((bufferp pdf-outline-pdf-document) + (error "PDF buffer was killed")) + (t + (or + (find-buffer-visiting + pdf-outline-pdf-document) + (find-file-noselect + pdf-outline-pdf-document))))) + (pdf-window + (if (and (window-live-p pdf-outline-pdf-window) + (eq buffer + (window-buffer pdf-outline-pdf-window))) + pdf-outline-pdf-window + (or (get-buffer-window buffer) + (and (null if-visible-p) + (display-buffer + buffer + '(nil (inhibit-same-window . t)))))))) + (setq pdf-outline-pdf-window pdf-window)))) + + +;; +;; Commands +;; + +(defun pdf-outline-move-to-current-page () + "Move to the item corresponding to the current page. + +Open nodes as necessary." + (interactive) + (let (page) + (with-selected-window (pdf-outline-get-pdf-window) + (setq page (pdf-view-current-page))) + (pdf-outline-move-to-page page))) + +(defun pdf-outline-quit-and-kill () + "Quit browsing the outline and kill it's buffer." + (interactive) + (pdf-outline-quit t)) + +(defun pdf-outline-quit (&optional kill) + "Quit browsing the outline buffer." + (interactive "P") + (let ((win (selected-window))) + (pdf-outline-select-pdf-window t) + (quit-window kill win))) + +(defun pdf-outline-up-heading (arg &optional invisible-ok) + "Like `outline-up-heading', but `push-mark' first." + (interactive "p") + (let ((pos (point))) + (outline-up-heading arg invisible-ok) + (unless (= pos (point)) + (push-mark pos)))) + +(defun pdf-outline-end-of-buffer () + "Move to the end of the outline buffer." + (interactive) + (let ((pos (point))) + (goto-char (point-max)) + (when (and (eobp) + (not (bobp)) + (null (button-at (point)))) + (forward-line -1)) + (unless (= pos (point)) + (push-mark pos)))) + +(defun pdf-outline-link-at-pos (&optional pos) + (unless pos (setq pos (point))) + (let ((button (or (button-at pos) + (button-at (1- pos))))) + (and button + (button-get button + 'pdf-outline-link)))) + +(defun pdf-outline-follow-link (&optional pos) + "Select PDF window and move to the page corresponding to POS." + (interactive) + (unless pos (setq pos (point))) + (let ((link (pdf-outline-link-at-pos pos))) + (unless link + (error "Nothing to follow here")) + (select-window (pdf-outline-get-pdf-window)) + (pdf-links-action-perform link))) + +(defun pdf-outline-follow-link-and-quit (&optional pos) + "Select PDF window and move to the page corresponding to POS. + +Then quit the outline window." + (interactive) + (let ((link (pdf-outline-link-at-pos (or pos (point))))) + (pdf-outline-quit) + (unless link + (error "Nothing to follow here")) + (pdf-links-action-perform link))) + +(defun pdf-outline-display-link (&optional pos) + "Display the page corresponding to the link at POS." + (interactive) + (unless pos (setq pos (point))) + (let ((inhibit-redisplay t) + (link (pdf-outline-link-at-pos pos))) + (unless link + (error "Nothing to follow here")) + (with-selected-window (pdf-outline-get-pdf-window) + (pdf-links-action-perform link)) + (force-mode-line-update t))) + +(defun pdf-outline-mouse-display-link (event) + "Display the page corresponding to the position of EVENT." + (interactive "@e") + (pdf-outline-display-link + (posn-point (event-start event)))) + +(defun pdf-outline-select-pdf-window (&optional no-create-p) + "Display and select the PDF document window." + (interactive) + (let ((win (pdf-outline-get-pdf-window no-create-p))) + (and (window-live-p win) + (select-window win)))) + +(defun pdf-outline-toggle-subtree () + "Toggle hidden state of the current complete subtree." + (interactive) + (save-excursion + (outline-back-to-heading) + (if (not (outline-invisible-p (line-end-position))) + (hide-subtree) + (show-subtree)))) + +(defun pdf-outline-move-to-page (page) + "Move to an outline item corresponding to PAGE." + (interactive + (list (or (and current-prefix-arg + (prefix-numeric-value current-prefix-arg)) + (read-number "Page: ")))) + (goto-char (pdf-outline-position-of-page page)) + (save-excursion + (while (outline-invisible-p) + (outline-up-heading 1 t) + (show-children))) + (save-excursion + (when (outline-invisible-p) + (outline-up-heading 1 t) + (show-children))) + (back-to-indentation)) + +(defun pdf-outline-position-of-page (page) + (let (curpage) + (save-excursion + (goto-char (point-min)) + (while (and (setq curpage (alist-get 'page (pdf-outline-link-at-pos))) + (< curpage page)) + (forward-line)) + (point)))) + + + +;; +;; Imenu Support +;; + + +;;;###autoload +(defun pdf-outline-imenu-enable () + "Enable imenu in the current PDF buffer." + (interactive) + (pdf-util-assert-pdf-buffer) + (setq-local imenu-create-index-function + (if pdf-outline-imenu-use-flat-menus + 'pdf-outline-imenu-create-index-flat + 'pdf-outline-imenu-create-index-tree)) + (imenu-add-to-menubar "PDF Outline")) + +(defun pdf-outline-imenu-disable () + "Disable imenu in the current PDF buffer." + (interactive) + (pdf-util-assert-pdf-buffer) + (setq-local imenu-create-index-function nil) + (local-set-key [menu-bar index] nil) + (when (eq pdf-view-mode-map + (keymap-parent (current-local-map))) + (use-local-map (keymap-parent (current-local-map))))) + + +(defun pdf-outline-imenu-create-item (link &optional labels) + (let-alist link + (list (format "%s (%s)" .title (if labels + (nth (1- .page) labels) + .page)) + 0 + 'pdf-outline-imenu-activate-link + link))) + +(defun pdf-outline-imenu-create-index-flat () + (let ((labels (and pdf-outline-display-labels + (pdf-info-pagelabels))) + index) + (dolist (item (pdf-info-outline)) + (let-alist item + (when (eq .type 'goto-dest) + (push (pdf-outline-imenu-create-item item labels) + index)))) + (nreverse index))) + + +(defun pdf-outline-imenu-create-index-tree () + (pdf-outline-imenu-create-index-tree-1 + (pdf-outline-treeify-outline-list + (cl-remove-if-not + (lambda (type) + (eq type 'goto-dest)) + (pdf-info-outline) + :key (apply-partially 'alist-get 'type))) + (and pdf-outline-display-labels + (pdf-info-pagelabels)))) + +(defun pdf-outline-imenu-create-index-tree-1 (nodes &optional labels) + (mapcar (lambda (node) + (let (children) + (when (consp (caar node)) + (setq children (cdr node) + node (car node))) + (let ((item + (pdf-outline-imenu-create-item node labels))) + (if children + (cons (alist-get 'title node) + (cons item (pdf-outline-imenu-create-index-tree-1 + children labels))) + item)))) + nodes)) + +(defun pdf-outline-treeify-outline-list (list) + (when list + (let ((depth (alist-get 'depth (car list))) + result) + (while (and list + (>= (alist-get 'depth (car list)) + depth)) + (when (= (alist-get 'depth (car list)) depth) + (let ((item (car list))) + (when (and (cdr list) + (> (alist-get 'depth (cadr list)) + depth)) + (setq item + (cons + item + (pdf-outline-treeify-outline-list (cdr list))))) + (push item result))) + (setq list (cdr list))) + (reverse result)))) + +(defun pdf-outline-imenu-activate-link (&rest args) + ;; bug #14029 + (when (eq (nth 2 args) 'pdf-outline-imenu-activate-link) + (setq args (cdr args))) + (pdf-links-action-perform (nth 2 args))) + +(defadvice imenu--split-menu (around pdf-outline activate) + "Advice to keep the original outline order. + + Calls `pdf-outline-imenu--split-menu' instead, if in a PDF + buffer and `pdf-outline-imenu-keep-order' is non-nil." + (if (not (and (pdf-util-pdf-buffer-p) + pdf-outline-imenu-keep-order)) + ad-do-it + (setq ad-return-value + (pdf-outline-imenu--split-menu menulist title)))) + +(defvar imenu--rescan-item) +(defvar imenu-sort-function) +(defvar imenu-create-index-function) +(defvar imenu-max-items) + +(defun pdf-outline-imenu--split-menu (menulist title) + "Replacement function for `imenu--split-menu'. + +This function does not move sub-menus to the top, therefore +keeping the original outline order of the document. Also it does +not call `imenu-sort-function'." + (let ((menulist (copy-sequence menulist)) + keep-at-top) + (if (memq imenu--rescan-item menulist) + (setq keep-at-top (list imenu--rescan-item) + menulist (delq imenu--rescan-item menulist))) + (if (> (length menulist) imenu-max-items) + (setq menulist + (mapcar + (lambda (menu) + (cons (format "From: %s" (caar menu)) menu)) + (imenu--split menulist imenu-max-items)))) + (cons title + (nconc (nreverse keep-at-top) menulist)))) + +;; bugfix for imenu in Emacs 24.3 and below. +(when (condition-case nil + (progn (imenu--truncate-items '(("" 0))) nil) + (error t)) + (eval-after-load "imenu" + '(defun imenu--truncate-items (menulist) + "Truncate all strings in MENULIST to `imenu-max-item-length'." + (mapc (lambda (item) + ;; Truncate if necessary. + (when (and (numberp imenu-max-item-length) + (> (length (car item)) imenu-max-item-length)) + (setcar item (substring (car item) 0 imenu-max-item-length))) + (when (imenu--subalist-p item) + (imenu--truncate-items (cdr item)))) + menulist)))) + + + +(provide 'pdf-outline) + +;;; pdf-outline.el ends here + +;; Local Variables: +;; byte-compile-warnings: (not obsolete) +;; End: diff --git a/elpa/pdf-tools-20211110.513/pdf-outline.elc b/elpa/pdf-tools-20211110.513/pdf-outline.elc Binary files differ. diff --git a/elpa/pdf-tools-20211110.513/pdf-sync.el b/elpa/pdf-tools-20211110.513/pdf-sync.el @@ -0,0 +1,780 @@ +;;; pdf-sync.el --- Use synctex to correlate LaTeX-Sources with PDF positions. -*- lexical-binding:t -*- +;; Copyright (C) 2013, 2014 Andreas Politz + +;; Author: Andreas Politz <politza@fh-trier.de> +;; Keywords: files, doc-view, pdf + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: +;; +;; The backward search uses a heuristic, which is pretty simple, but +;; effective: It extracts the text around the click-position in the +;; PDF, normalizes it's whitespace, deletes certain notorious +;; character and translates certain other character into their latex +;; equivalents. This transformed text is split into a series of +;; token. A similar operation is performed on the source code around +;; the position synctex points at. These two sequences of token are +;; aligned with a standard sequence alignment algorithm, resulting in +;; an alist of matched and unmatched tokens. This is then used to +;; find the corresponding word from the PDF file in the LaTeX buffer. + + +(require 'pdf-view) +(require 'pdf-info) +(require 'pdf-util) +(require 'let-alist) + +;;; Code: + +(defgroup pdf-sync nil + "Jump from TeX sources to PDF pages and back." + :group 'pdf-tools) + +(defcustom pdf-sync-forward-display-pdf-key "C-c C-g" + "Key to jump from a TeX buffer to it's PDF file. + +This key is added to `TeX-source-correlate-method', when +command `pdf-sync-minor-mode' is activated and this map is defined." + :group 'pdf-sync + :type 'key-sequence) + +(make-obsolete-variable + 'pdf-sync-forward-display-pdf-key + "Bound in Auctex's to C-c C-v, if TeX-source-correlate-mode is activate." "1.0") + +(defcustom pdf-sync-backward-hook nil + "Hook ran after going to a source location. + +The hook is run in the TeX buffer." + :group 'pdf-sync + :type 'hook + :options '(pdf-sync-backward-beginning-of-word)) + +(defcustom pdf-sync-forward-hook nil + "Hook ran after displaying the PDF buffer. + +The hook is run in the PDF's buffer." + :group 'pdf-sync + :type 'hook) + +(defcustom pdf-sync-forward-display-action nil + "Display action used when displaying PDF buffers." + :group 'pdf-sync + :type 'display-buffer--action-custom-type) + +(defcustom pdf-sync-backward-display-action nil + "Display action used when displaying TeX buffers." + :group 'pdf-sync + :type 'display-buffer--action-custom-type) + +(defcustom pdf-sync-locate-synctex-file-functions nil + "A list of functions for locating the synctex database. + +Each function on this hook should accept a single argument: The +absolute path of a PDF file. It should return the absolute path +of the corresponding synctex database or nil, if it was unable to +locate it." + :group 'pdf-sync + :type 'hook) + +(defvar pdf-sync-minor-mode-map + (let ((kmap (make-sparse-keymap))) + (define-key kmap [double-mouse-1] 'pdf-sync-backward-search-mouse) + (define-key kmap [C-mouse-1] 'pdf-sync-backward-search-mouse) + kmap)) + +(defcustom pdf-sync-backward-redirect-functions nil + "List of functions which may redirect a backward search. + +Functions on this hook should accept three arguments, namely +SOURCE, LINE and COLUMN, where SOURCE is the absolute filename of +the source file and LINE and COLUMN denote the position in the +file. COLUMN may be negative, meaning unspecified. + +These functions should either return nil, if no redirection is +necessary. Or a list of the same structure, with some or all (or +none) values modified. + +AUCTeX installs a function here which changes the backward search +location for synthetic `TeX-region' files back to the equivalent +position in the original tex file." + :group 'pdf-sync + :type '(repeat function)) + + +;;;###autoload +(define-minor-mode pdf-sync-minor-mode + "Correlate a PDF position with the TeX file. +\\<pdf-sync-minor-mode-map> +This works via SyncTeX, which means the TeX sources need to have +been compiled with `--synctex=1'. In AUCTeX this can be done by +setting `TeX-source-correlate-method' to 'synctex \(before AUCTeX +is loaded\) and enabling `TeX-source-correlate-mode'. + +Then \\[pdf-sync-backward-search-mouse] in the PDF buffer will open the +corresponding TeX location. + +If AUCTeX is your preferred tex-mode, this library arranges to +bind `pdf-sync-forward-display-pdf-key' \(the default is `C-c C-g'\) +to `pdf-sync-forward-search' in `TeX-source-correlate-map'. This +function displays the PDF page corresponding to the current +position in the TeX buffer. This function only works together +with AUCTeX." + :group 'pdf-sync + (pdf-util-assert-pdf-buffer)) + + +;; * ================================================================== * +;; * Backward search (PDF -> TeX) +;; * ================================================================== * + +(defcustom pdf-sync-backward-use-heuristic t + "Whether to apply a heuristic when backward searching. + +If nil, just go where Synctex tells us. Otherwise try to find +the exact location of the clicked-upon text in the PDF." + :group 'pdf-sync + :type 'boolean) + +(defcustom pdf-sync-backward-text-translations + '((88 "X" "sum") + (94 "textasciicircum") + (126 "textasciitilde") + (169 "copyright" "textcopyright") + (172 "neg" "textlnot") + (174 "textregistered" "textregistered") + (176 "textdegree") + (177 "pm" "textpm") + (181 "upmu" "mu") + (182 "mathparagraph" "textparagraph" "P" "textparagraph") + (215 "times") + (240 "eth" "dh") + (915 "Upgamma" "Gamma") + (920 "Uptheta" "Theta") + (923 "Uplambda" "Lambda") + (926 "Upxi" "Xi") + (928 "Uppi" "Pi") + (931 "Upsigma" "Sigma") + (933 "Upupsilon" "Upsilon") + (934 "Upphi" "Phi") + (936 "Uppsi" "Psi") + (945 "upalpha" "alpha") + (946 "upbeta" "beta") + (947 "upgamma" "gamma") + (948 "updelta" "delta") + (949 "upvarepsilon" "varepsilon") + (950 "upzeta" "zeta") + (951 "upeta" "eta") + (952 "uptheta" "theta") + (953 "upiota" "iota") + (954 "upkappa" "varkappa" "kappa") + (955 "uplambda" "lambda") + (957 "upnu" "nu") + (958 "upxi" "xi") + (960 "uppi" "pi") + (961 "upvarrho" "uprho" "rho") + (962 "varsigma") + (963 "upvarsigma" "upsigma" "sigma") + (964 "uptau" "tau") + (965 "upupsilon" "upsilon") + (966 "upphi" "phi") + (967 "upchi" "chi") + (968 "uppsi" "psi") + (969 "upomega" "omega") + (977 "upvartheta" "vartheta") + (981 "upvarphi" "varphi") + (8224 "dagger") + (8225 "ddagger") + (8226 "bullet") + (8486 "Upomega" "Omega") + (8501 "aleph") + (8592 "mapsfrom" "leftarrow") + (8593 "uparrow") + (8594 "to" "mapsto" "rightarrow") + (8595 "downarrow") + (8596 "leftrightarrow") + (8656 "shortleftarrow" "Leftarrow") + (8657 "Uparrow") + (8658 "Mapsto" "rightrightarrows" "Rightarrow") + (8659 "Downarrow") + (8660 "Leftrightarrow") + (8704 "forall") + (8706 "partial") + (8707 "exists") + (8709 "varnothing" "emptyset") + (8710 "Updelta" "Delta") + (8711 "nabla") + (8712 "in") + (8722 "-") + (8725 "setminus") + (8727 "*") + (8734 "infty") + (8743 "wedge") + (8744 "vee") + (8745 "cap") + (8746 "cup") + (8756 "therefore") + (8757 "because") + (8764 "thicksim" "sim") + (8776 "thickapprox" "approx") + (8801 "equiv") + (8804 "leq") + (8805 "geq") + (8810 "lll") + (8811 "ggg") + (8814 "nless") + (8815 "ngtr") + (8822 "lessgtr") + (8823 "gtrless") + (8826 "prec") + (8832 "nprec") + (8834 "subset") + (8835 "supset") + (8838 "subseteq") + (8839 "supseteq") + (8853 "oplus") + (8855 "otimes") + (8869 "bot" "perp") + (9702 "circ") + (9792 "female" "venus") + (9793 "earth") + (9794 "male" "mars") + (9824 "spadesuit") + (9827 "clubsuit") + (9829 "heartsuit") + (9830 "diamondsuit")) + "Alist mapping PDF character to a list of LaTeX macro names. + +Adding a character here with it's LaTeX equivalent names allows +the heuristic backward search to find it's location in the source +file. These strings should not match +`pdf-sync-backward-source-flush-regexp'. + +Has no effect if `pdf-sync-backward-use-heuristic' is nil." + :group 'pdf-sync + :type '(alist :key-type character + :value-type (repeat string))) + +(defconst pdf-sync-backward-text-flush-regexp + "[][.·{}|\\]\\|\\C.\\|-\n+" + "Regexp of ignored text when backward searching.") + +(defconst pdf-sync-backward-source-flush-regexp + "\\(?:\\\\\\(?:begin\\|end\\|\\(?:eq\\)?ref\\|label\\|cite\\){[^}]*}\\)\\|[][\\&{}$_]" + "Regexp of ignored source when backward searching.") + +(defconst pdf-sync-backward-context-limit 64 + "Number of character to include in the backward search.") + +(defun pdf-sync-backward-search-mouse (ev) + "Go to the source corresponding to position at event EV." + (interactive "@e") + (let* ((posn (event-start ev)) + (image (posn-image posn)) + (xy (posn-object-x-y posn))) + (unless image + (error "Outside of image area")) + (pdf-sync-backward-search (car xy) (cdr xy)))) + +(defun pdf-sync-backward-search (x y) + "Go to the source corresponding to image coordinates X, Y. + +Try to find the exact position, if +`pdf-sync-backward-use-heuristic' is non-nil." + (cl-destructuring-bind (source finder) + (pdf-sync-backward-correlate x y) + (pop-to-buffer (or (find-buffer-visiting source) + (find-file-noselect source)) + pdf-sync-backward-display-action) + (push-mark) + (funcall finder) + (run-hooks 'pdf-sync-backward-hook))) + +(defun pdf-sync-backward-correlate (x y) + "Find the source corresponding to image coordinates X, Y. + +Returns a list \(SOURCE FINDER\), where SOURCE is the name of the +TeX file and FINDER a function of zero arguments which, when +called in the buffer of the aforementioned file, will try to move +point to the correct position." + + (pdf-util-assert-pdf-window) + (let ((size (pdf-view-image-size)) + (page (pdf-view-current-page))) + (setq x (/ x (float (car size))) + y (/ y (float (cdr size)))) + (let-alist (pdf-info-synctex-backward-search page x y) + (let ((data (list (expand-file-name .filename) + .line .column))) + (cl-destructuring-bind (source line column) + (or (save-selected-window + (apply 'run-hook-with-args-until-success + 'pdf-sync-backward-redirect-functions data)) + data) + (list source + (if (not pdf-sync-backward-use-heuristic) + (lambda nil + (pdf-util-goto-position line column)) + (let ((context (pdf-sync-backward--get-text-context page x y))) + (lambda nil + (pdf-sync-backward--find-position line column context)))))))))) + +(defun pdf-sync-backward--find-position (line column context) + (pdf-util-goto-position line column) + (cl-destructuring-bind (windex chindex words) + context + (let* ((swords (pdf-sync-backward--get-source-context + nil (* 6 pdf-sync-backward-context-limit))) + (similarity-fn (lambda (text source) + (if (if (consp text) + (member source text) + (equal text source)) + 1024 -1024))) + (alignment + (pdf-util-seq-alignment + words swords similarity-fn 'infix))) + (setq alignment (cl-remove-if-not 'car (cdr alignment))) + (cl-assert (< windex (length alignment))) + + (let ((word (cdr (nth windex alignment)))) + (unless word + (setq chindex 0 + word (cdr (nth (1+ windex) alignment)))) + (unless word + (setq word (cdr (nth (1- windex) alignment)) + chindex (length word))) + (when word + (cl-assert (get-text-property 0 'position word) t) + (goto-char (get-text-property 0 'position word)) + (forward-char chindex)))))) + +(defun pdf-sync-backward--get-source-context (&optional position limit) + (save-excursion + (when position (goto-char position)) + (goto-char (line-beginning-position)) + (let* ((region + (cond + ((eq limit 'line) + (cons (line-beginning-position) + (line-end-position))) + + ;; Synctex usually jumps to the end macro, in case it + ;; does not understand the environment. + ((and (fboundp 'LaTeX-find-matching-begin) + (looking-at " *\\\\\\(end\\){")) + (cons (or (ignore-errors + (save-excursion + (LaTeX-find-matching-begin) + (forward-line 1) + (point))) + (point)) + (point))) + ((and (fboundp 'LaTeX-find-matching-end) + (looking-at " *\\\\\\(begin\\){")) + (goto-char (line-end-position)) + (cons (point) + (or (ignore-errors + (save-excursion + (LaTeX-find-matching-end) + (forward-line 0) + (point))) + (point)))) + (t (cons (point) (point))))) + (begin (car region)) + (end (cdr region))) + (when (numberp limit) + (let ((delta (- limit (- end begin)))) + (when (> delta 0) + (setq begin (max (point-min) + (- begin (/ delta 2))) + end (min (point-max) + (+ end (/ delta 2))))))) + (let ((string (buffer-substring-no-properties begin end))) + (dotimes (i (length string)) + (put-text-property i (1+ i) 'position (+ begin i) string)) + (nth 2 (pdf-sync-backward--tokenize + (pdf-sync-backward--source-strip-comments string) + nil + pdf-sync-backward-source-flush-regexp)))))) + +(defun pdf-sync-backward--source-strip-comments (string) + "Strip all standard LaTeX comments from string." + (with-temp-buffer + (save-excursion (insert string)) + (while (re-search-forward + "^\\(?:[^\\\n]\\|\\(?:\\\\\\\\\\)\\)*\\(%.*\\)" nil t) + (delete-region (match-beginning 1) (match-end 1))) + (buffer-string))) + +(defun pdf-sync-backward--get-text-context (page x y) + (cl-destructuring-bind (&optional char edges) + (car (pdf-info-charlayout page (cons x y))) + (when edges + (setq x (nth 0 edges) + y (nth 1 edges))) + (let* ((prefix (pdf-info-gettext page (list 0 0 x y))) + (suffix (pdf-info-gettext page (list x y 1 1))) + (need-suffix-space-p (memq char '(?\s ?\n))) + ;; Figure out whether we missed a space by matching the + ;; prefix's suffix with the line's prefix. Due to the text + ;; extraction in poppler, spaces are only inserted in + ;; between words. This test may fail, if prefix and line + ;; do not overlap, which may happen in various cases, but + ;; we don't care. + (need-prefix-space-p + (and (not need-suffix-space-p) + (memq + (ignore-errors + (aref (pdf-info-gettext page (list x y x y) 'line) + (- (length prefix) + (or (cl-position ?\n prefix :from-end t) + -1) + 1))) + '(?\s ?\n))))) + (setq prefix + (concat + (substring + prefix (max 0 (min (1- (length prefix)) + (- (length prefix) + pdf-sync-backward-context-limit)))) + (if need-prefix-space-p " ")) + suffix + (concat + (if need-suffix-space-p " ") + (substring + suffix 0 (max 0 (min (1- (length suffix)) + pdf-sync-backward-context-limit))))) + (pdf-sync-backward--tokenize + prefix suffix + pdf-sync-backward-text-flush-regexp + pdf-sync-backward-text-translations)))) + +(defun pdf-sync-backward--tokenize (prefix &optional suffix flush-re translation) + (with-temp-buffer + (when prefix (insert prefix)) + (let* ((center (copy-marker (point))) + (case-fold-search nil)) + (when suffix (insert suffix)) + (goto-char 1) + ;; Delete ignored text. + (when flush-re + (save-excursion + (while (re-search-forward flush-re nil t) + (replace-match " " t t)))) + ;; Normalize whitespace. + (save-excursion + (while (re-search-forward "[ \t\f\n]+" nil t) + (replace-match " " t t))) + ;; Split words and non-words + (save-excursion + (while (re-search-forward "[^ ]\\b\\|[^ [:alnum:]]" nil t) + (insert-before-markers " "))) + ;; Replace character + (let ((translate + (lambda (string) + (or (and (= (length string) 1) + (cdr (assq (aref string 0) + translation))) + string))) + words + (windex -1) + (chindex 0)) + (skip-chars-forward " ") + (while (and (not (eobp)) + (<= (point) center)) + (cl-incf windex) + (skip-chars-forward "^ ") + (skip-chars-forward " ")) + (goto-char center) + (when (eq ?\s (char-after)) + (skip-chars-backward " ")) + (setq chindex (- (skip-chars-backward "^ "))) + (setq words (split-string (buffer-string))) + (when translation + (setq words (mapcar translate words))) + (list windex chindex words))))) + +(defun pdf-sync-backward-beginning-of-word () + "Maybe move to the beginning of the word. + +Don't move if already at the beginning, or if not at a word +character. + +This function is meant to be put on `pdf-sync-backward-hook', when +word-level searching is desired." + (interactive) + (unless (or (looking-at "\\b\\w") + (not (looking-back "\\w" (1- (point))))) + (backward-word))) + +;; * ------------------------------------------------------------------ * +;; * Debugging backward search +;; * ------------------------------------------------------------------ * + +(defvar pdf-sync-backward-debug-trace nil) + +(defun pdf-sync-backward-debug-wrapper (fn-symbol fn &rest args) + (cond + ((eq fn-symbol 'pdf-sync-backward-search) + (setq pdf-sync-backward-debug-trace nil) + (apply fn args)) + (t + (let ((retval (apply fn args))) + (push `(,args . ,retval) + pdf-sync-backward-debug-trace) + retval)))) + +(define-minor-mode pdf-sync-backward-debug-minor-mode + "Aid in debugging the backward search." + :group 'pdf-sync + (if (and (fboundp 'advice-add) + (fboundp 'advice-remove)) + (let ((functions + '(pdf-sync-backward-search + pdf-sync-backward--tokenize + pdf-util-seq-alignment))) + (cond + (pdf-sync-backward-debug-minor-mode + (dolist (fn functions) + (advice-add fn :around (apply-partially 'pdf-sync-backward-debug-wrapper + fn) + `((name . ,(format "%s-debug" fn)))))) + (t + (dolist (fn functions) + (advice-remove fn (format "%s-debug" fn)))))) + (error "Need Emacs version >= 24.4"))) + +(defun pdf-sync-backward-debug-explain () + "Explain the last backward search. + +Needs to have `pdf-sync-backward-debug-minor-mode' enabled." + + (interactive) + (unless pdf-sync-backward-debug-trace + (error "No last search or `pdf-sync-backward-debug-minor-mode' not enabled.")) + + (with-current-buffer (get-buffer-create "*pdf-sync-backward trace*") + (cl-destructuring-bind (text source alignment &rest ignored) + (reverse pdf-sync-backward-debug-trace) + (let* ((fill-column 68) + (sep (format "\n%s\n" (make-string fill-column ?-))) + (highlight '(:background "chartreuse" :foreground "black")) + (or-sep "|") + (inhibit-read-only t) + (windex (nth 0 (cdr text))) + (chindex (nth 1 (cdr text)))) + (erase-buffer) + (font-lock-mode -1) + (view-mode 1) + (insert (propertize "Text Raw:" 'face 'font-lock-keyword-face)) + (insert sep) + (insert (nth 0 (car text))) + (insert (propertize "<|>" 'face highlight)) + (insert (nth 1 (car text))) + (insert sep) + (insert (propertize "Text Token:" 'face 'font-lock-keyword-face)) + (insert sep) + (fill-region (point) + (progn + (insert + (mapconcat (lambda (elt) + (if (consp elt) + (mapconcat 'identity elt or-sep) + elt)) + (nth 2 (cdr text)) " ")) + (point))) + (insert sep) + + (insert (propertize "Source Raw:" 'face 'font-lock-keyword-face)) + (insert sep) + (insert (nth 0 (car source))) + (insert sep) + (insert (propertize "Source Token:" 'face 'font-lock-keyword-face)) + (insert sep) + (fill-region (point) + (progn (insert (mapconcat 'identity (nth 2 (cdr source)) " ")) + (point))) + (insert sep) + + (insert (propertize "Alignment:" 'face 'font-lock-keyword-face)) + (insert (format " (windex=%d, chindex=%d" windex chindex)) + (insert sep) + (save-excursion (newline 2)) + (let ((column 0) + (index 0)) + (dolist (a (cdr (cdr alignment))) + (let* ((source (cdr a)) + (text (if (consp (car a)) + (mapconcat 'identity (car a) or-sep) + (car a))) + (extend (max (length text) + (length source)))) + (when (and (not (bolp)) + (> (+ column extend) + fill-column)) + (forward-line 2) + (newline 3) + (forward-line -2) + (setq column 0)) + (when text + (insert (propertize text 'face + (if (= index windex) + highlight + (if source 'match + 'lazy-highlight))))) + (move-to-column (+ column extend) t) + (insert " ") + (save-excursion + (forward-line) + (move-to-column column t) + (when source + (insert (propertize source 'face (if text + 'match + 'lazy-highlight)))) + (move-to-column (+ column extend) t) + (insert " ")) + (cl-incf column (+ 1 extend)) + (when text (cl-incf index))))) + (goto-char (point-max)) + (insert sep) + (goto-char 1) + (pop-to-buffer (current-buffer)))))) + + +;; * ================================================================== * +;; * Forward search (TeX -> PDF) +;; * ================================================================== * + +(defun pdf-sync-forward-search (&optional line column) + "Display the PDF location corresponding to LINE, COLUMN." + (interactive) + (cl-destructuring-bind (pdf page _x1 y1 _x2 _y2) + (pdf-sync-forward-correlate line column) + (let ((buffer (or (find-buffer-visiting pdf) + (find-file-noselect pdf)))) + (with-selected-window (display-buffer + buffer pdf-sync-forward-display-action) + (pdf-util-assert-pdf-window) + (when page + (pdf-view-goto-page page) + (when y1 + (let ((top (* y1 (cdr (pdf-view-image-size))))) + (pdf-util-tooltip-arrow (round top)))))) + (with-current-buffer buffer + (run-hooks 'pdf-sync-forward-hook))))) + +(defun pdf-sync-forward-correlate (&optional line column) + "Find the PDF location corresponding to LINE, COLUMN. + +Returns a list \(PDF PAGE X1 Y1 X2 Y2\), where PAGE, X1, Y1, X2 +and Y2 may be nil, if the destination could not be found." + (unless (fboundp 'TeX-master-file) + (error "This function works only with AUCTeX")) + (unless line (setq line (line-number-at-pos))) + (unless column (setq column (current-column))) + + (let* ((pdf (expand-file-name + (with-no-warnings (TeX-master-file "pdf")))) + (sfilename (pdf-sync-synctex-file-name + (buffer-file-name) pdf))) + (cons pdf + (condition-case error + (let-alist (pdf-info-synctex-forward-search + (or sfilename + (buffer-file-name)) + line column pdf) + (cons .page .edges)) + (error + (message "%s" (error-message-string error)) + (list nil nil nil nil nil)))))) + + + +;; * ================================================================== * +;; * Dealing with synctex files. +;; * ================================================================== * + +(defun pdf-sync-locate-synctex-file (pdffile) + "Locate the synctex database corresponding to PDFFILE. + +Returns either the absolute path of the database or nil. + +See also `pdf-sync-locate-synctex-file-functions'." + (cl-check-type pdffile string) + (setq pdffile (expand-file-name pdffile)) + (or (run-hook-with-args-until-success + 'pdf-sync-locate-synctex-file-functions pdffile) + (pdf-sync-locate-synctex-file-default pdffile))) + +(defun pdf-sync-locate-synctex-file-default (pdffile) + "The default function for locating a synctex database for PDFFILE. + +See also `pdf-sync-locate-synctex-file'." + (let ((default-directory + (file-name-directory pdffile)) + (basename (file-name-sans-extension + (file-name-nondirectory pdffile)))) + (cl-labels ((file-if-exists-p (file) + (and (file-exists-p file) + file))) + (or (file-if-exists-p + (expand-file-name (concat basename ".synctex.gz"))) + (file-if-exists-p + (expand-file-name (concat basename ".synctex"))) + ;; Some pdftex quote the basename. + (file-if-exists-p + (expand-file-name (concat "\"" basename "\"" ".synctex.gz"))) + (file-if-exists-p + (expand-file-name (concat "\"" basename "\"" ".synctex"))))))) + +(defun pdf-sync-synctex-file-name (filename pdffile) + "Find SyncTeX filename corresponding to FILENAME in the context of PDFFILE. + +This function consults the synctex.gz database of PDFFILE and +searches for a filename, which is `file-equal-p' to FILENAME. +The first such filename is returned, or nil if none was found." + + (when (file-exists-p filename) + (setq filename (expand-file-name filename)) + (let* ((synctex (pdf-sync-locate-synctex-file pdffile)) + (basename (file-name-nondirectory filename)) + (regexp (format "^ *Input *: *[^:\n]+ *:\\(.*%s\\)$" + (regexp-quote basename))) + (jka-compr-verbose nil)) + (when (and synctex + (file-readable-p synctex)) + (with-current-buffer (find-file-noselect synctex :nowarn) + (unless (or (verify-visited-file-modtime) + (buffer-modified-p)) + (revert-buffer :ignore-auto :noconfirm) + (goto-char (point-min))) + ;; Keep point in front of the found filename. It will + ;; probably be queried for again next time. + (let ((beg (point)) + (end (point-max))) + (catch 'found + (dotimes (_x 2) + (while (re-search-forward regexp end t) + (let ((syncname (match-string-no-properties 1))) + (when (and (file-exists-p syncname) + (file-equal-p filename syncname)) + (goto-char (point-at-bol)) + (throw 'found syncname)))) + (setq end beg + beg (point-min)) + (goto-char beg))))))))) + +(provide 'pdf-sync) +;;; pdf-sync.el ends here diff --git a/elpa/pdf-tools-20211110.513/pdf-sync.elc b/elpa/pdf-tools-20211110.513/pdf-sync.elc Binary files differ. diff --git a/elpa/pdf-tools-20211110.513/pdf-tools-autoloads.el b/elpa/pdf-tools-20211110.513/pdf-tools-autoloads.el @@ -0,0 +1,502 @@ +;;; pdf-tools-autoloads.el --- automatically extracted autoloads +;; +;;; Code: + +(add-to-list 'load-path (directory-file-name + (or (file-name-directory #$) (car load-path)))) + + +;;;### (autoloads nil "pdf-annot" "pdf-annot.el" (0 0 0 0)) +;;; Generated autoloads from pdf-annot.el + +(autoload 'pdf-annot-minor-mode "pdf-annot" "\ +Support for PDF Annotations. + +If called interactively, enable Pdf-Annot minor mode if ARG is +positive, and disable it if ARG is zero or negative. If called +from Lisp, also enable the mode if ARG is omitted or nil, and +toggle it if ARG is `toggle'; disable the mode otherwise. + +\\{pdf-annot-minor-mode-map} + +\(fn &optional ARG)" t nil) + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "pdf-annot" '("pdf-annot-"))) + +;;;*** + +;;;### (autoloads nil "pdf-cache" "pdf-cache.el" (0 0 0 0)) +;;; Generated autoloads from pdf-cache.el + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "pdf-cache" '("boundingbox" "define-pdf-cache-function" "page" "pdf-cache-" "textregions"))) + +;;;*** + +;;;### (autoloads nil "pdf-dev" "pdf-dev.el" (0 0 0 0)) +;;; Generated autoloads from pdf-dev.el + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "pdf-dev" '("pdf-dev-"))) + +;;;*** + +;;;### (autoloads nil "pdf-history" "pdf-history.el" (0 0 0 0)) +;;; Generated autoloads from pdf-history.el + +(autoload 'pdf-history-minor-mode "pdf-history" "\ +Keep a history of previously visited pages. + +If called interactively, enable Pdf-History minor mode if ARG is +positive, and disable it if ARG is zero or negative. If called +from Lisp, also enable the mode if ARG is omitted or nil, and +toggle it if ARG is `toggle'; disable the mode otherwise. + +This is a simple stack-based history. Turning the page or +following a link pushes the left-behind page on the stack, which +may be navigated with the following keys. + +\\{pdf-history-minor-mode-map} + +\(fn &optional ARG)" t nil) + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "pdf-history" '("pdf-history-"))) + +;;;*** + +;;;### (autoloads nil "pdf-info" "pdf-info.el" (0 0 0 0)) +;;; Generated autoloads from pdf-info.el + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "pdf-info" '("pdf-info-"))) + +;;;*** + +;;;### (autoloads nil "pdf-isearch" "pdf-isearch.el" (0 0 0 0)) +;;; Generated autoloads from pdf-isearch.el + +(autoload 'pdf-isearch-minor-mode "pdf-isearch" "\ +Isearch mode for PDF buffer. + +If called interactively, enable Pdf-Isearch minor mode if ARG is +positive, and disable it if ARG is zero or negative. If called +from Lisp, also enable the mode if ARG is omitted or nil, and +toggle it if ARG is `toggle'; disable the mode otherwise. + +When this mode is enabled \\[isearch-forward], among other keys, +starts an incremental search in this PDF document. Since this mode +uses external programs to highlight found matches via +image-processing, proceeding to the next match may be slow. + +Therefore two isearch behaviours have been defined: Normal isearch and +batch mode. The later one is a minor mode +\(`pdf-isearch-batch-mode'), which when activated inhibits isearch +from stopping at and highlighting every single match, but rather +display them batch-wise. Here a batch means a number of matches +currently visible in the selected window. + +The kind of highlighting is determined by three faces +`pdf-isearch-match' (for the current match), `pdf-isearch-lazy' +\(for all other matches) and `pdf-isearch-batch' (when in batch +mode), which see. + +Colors may also be influenced by the minor-mode +`pdf-view-dark-minor-mode'. If this is minor mode enabled, each face's +dark colors, are used (see e.g. `frame-background-mode'), instead +of the light ones. + +\\{pdf-isearch-minor-mode-map} +While in `isearch-mode' the following keys are available. Note +that not every isearch command work as expected. + +\\{pdf-isearch-active-mode-map} + +\(fn &optional ARG)" t nil) + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "pdf-isearch" '("pdf-isearch-"))) + +;;;*** + +;;;### (autoloads nil "pdf-links" "pdf-links.el" (0 0 0 0)) +;;; Generated autoloads from pdf-links.el + +(autoload 'pdf-links-minor-mode "pdf-links" "\ +Handle links in PDF documents.\\<pdf-links-minor-mode-map> + +If called interactively, enable Pdf-Links minor mode if ARG is +positive, and disable it if ARG is zero or negative. If called +from Lisp, also enable the mode if ARG is omitted or nil, and +toggle it if ARG is `toggle'; disable the mode otherwise. + +If this mode is enabled, most links in the document may be +activated by clicking on them or by pressing \\[pdf-links-action-perform] and selecting +one of the displayed keys, or by using isearch limited to +links via \\[pdf-links-isearch-link]. + +\\{pdf-links-minor-mode-map} + +\(fn &optional ARG)" t nil) + +(autoload 'pdf-links-action-perform "pdf-links" "\ +Follow LINK, depending on its type. + +This may turn to another page, switch to another PDF buffer or +invoke `pdf-links-browse-uri-function'. + +Interactively, link is read via `pdf-links-read-link-action'. +This function displays characters around the links in the current +page and starts reading characters (ignoring case). After a +sufficient number of characters have been read, the corresponding +link's link is invoked. Additionally, SPC may be used to +scroll the current page. + +\(fn LINK)" t nil) + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "pdf-links" '("pdf-links-"))) + +;;;*** + +;;;### (autoloads nil "pdf-loader" "pdf-loader.el" (0 0 0 0)) +;;; Generated autoloads from pdf-loader.el + +(autoload 'pdf-loader-install "pdf-loader" "\ +Prepare Emacs for using PDF Tools. + +This function acts as a replacement for `pdf-tools-install' and +makes Emacs load and use PDF Tools as soon as a PDF file is +opened, but not sooner. + +The arguments are passed verbatim to `pdf-tools-install', which +see. + +\(fn &optional NO-QUERY-P SKIP-DEPENDENCIES-P NO-ERROR-P FORCE-DEPENDENCIES-P)" nil nil) + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "pdf-loader" '("pdf-loader--"))) + +;;;*** + +;;;### (autoloads nil "pdf-macs" "pdf-macs.el" (0 0 0 0)) +;;; Generated autoloads from pdf-macs.el + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "pdf-macs" '("pdf-view-"))) + +;;;*** + +;;;### (autoloads nil "pdf-misc" "pdf-misc.el" (0 0 0 0)) +;;; Generated autoloads from pdf-misc.el + +(autoload 'pdf-misc-minor-mode "pdf-misc" "\ +FIXME: Not documented. + +If called interactively, enable Pdf-Misc minor mode if ARG is +positive, and disable it if ARG is zero or negative. If called +from Lisp, also enable the mode if ARG is omitted or nil, and +toggle it if ARG is `toggle'; disable the mode otherwise. + +\(fn &optional ARG)" t nil) + +(autoload 'pdf-misc-size-indication-minor-mode "pdf-misc" "\ +Provide a working size indication in the mode-line. + +If called interactively, enable Pdf-Misc-Size-Indication minor +mode if ARG is positive, and disable it if ARG is zero or +negative. If called from Lisp, also enable the mode if ARG is +omitted or nil, and toggle it if ARG is `toggle'; disable the +mode otherwise. + +\(fn &optional ARG)" t nil) + +(autoload 'pdf-misc-menu-bar-minor-mode "pdf-misc" "\ +Display a PDF Tools menu in the menu-bar. + +If called interactively, enable Pdf-Misc-Menu-Bar minor mode if +ARG is positive, and disable it if ARG is zero or negative. If +called from Lisp, also enable the mode if ARG is omitted or nil, +and toggle it if ARG is `toggle'; disable the mode otherwise. + +\(fn &optional ARG)" t nil) + +(autoload 'pdf-misc-context-menu-minor-mode "pdf-misc" "\ +Provide a right-click context menu in PDF buffers. + +If called interactively, enable Pdf-Misc-Context-Menu minor mode +if ARG is positive, and disable it if ARG is zero or negative. +If called from Lisp, also enable the mode if ARG is omitted or +nil, and toggle it if ARG is `toggle'; disable the mode +otherwise. + +\\{pdf-misc-context-menu-minor-mode-map} + +\(fn &optional ARG)" t nil) + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "pdf-misc" '("pdf-misc-"))) + +;;;*** + +;;;### (autoloads nil "pdf-occur" "pdf-occur.el" (0 0 0 0)) +;;; Generated autoloads from pdf-occur.el + +(autoload 'pdf-occur "pdf-occur" "\ +List lines matching STRING or PCRE. + +Interactively search for a regexp. Unless a prefix arg was given, +in which case this functions performs a string search. + +If `pdf-occur-prefer-string-search' is non-nil, the meaning of +the prefix-arg is inverted. + +\(fn STRING &optional REGEXP-P)" t nil) + +(autoload 'pdf-occur-multi-command "pdf-occur" "\ +Perform `pdf-occur' on multiple buffer. + +For a programmatic search of multiple documents see +`pdf-occur-search'." t nil) + +(defvar pdf-occur-global-minor-mode nil "\ +Non-nil if Pdf-Occur-Global minor mode is enabled. +See the `pdf-occur-global-minor-mode' command +for a description of this minor mode. +Setting this variable directly does not take effect; +either customize it (see the info node `Easy Customization') +or call the function `pdf-occur-global-minor-mode'.") + +(custom-autoload 'pdf-occur-global-minor-mode "pdf-occur" nil) + +(autoload 'pdf-occur-global-minor-mode "pdf-occur" "\ +Enable integration of Pdf Occur with other modes. + +If called interactively, enable Pdf-Occur-Global minor mode if +ARG is positive, and disable it if ARG is zero or negative. If +called from Lisp, also enable the mode if ARG is omitted or nil, +and toggle it if ARG is `toggle'; disable the mode otherwise. + +This global minor mode enables (or disables) +`pdf-occur-ibuffer-minor-mode' and `pdf-occur-dired-minor-mode' +in all current and future ibuffer/dired buffer. + +\(fn &optional ARG)" t nil) + +(autoload 'pdf-occur-ibuffer-minor-mode "pdf-occur" "\ +Hack into ibuffer's do-occur binding. + +If called interactively, enable Pdf-Occur-Ibuffer minor mode if +ARG is positive, and disable it if ARG is zero or negative. If +called from Lisp, also enable the mode if ARG is omitted or nil, +and toggle it if ARG is `toggle'; disable the mode otherwise. + +This mode remaps `ibuffer-do-occur' to +`pdf-occur-ibuffer-do-occur', which will start the PDF Tools +version of `occur', if all marked buffer's are in `pdf-view-mode' +and otherwise fallback to `ibuffer-do-occur'. + +\(fn &optional ARG)" t nil) + +(autoload 'pdf-occur-dired-minor-mode "pdf-occur" "\ +Hack into dired's `dired-do-search' binding. + +If called interactively, enable Pdf-Occur-Dired minor mode if ARG +is positive, and disable it if ARG is zero or negative. If +called from Lisp, also enable the mode if ARG is omitted or nil, +and toggle it if ARG is `toggle'; disable the mode otherwise. + +This mode remaps `dired-do-search' to +`pdf-occur-dired-do-search', which will start the PDF Tools +version of `occur', if all marked buffer's are in `pdf-view-mode' +and otherwise fallback to `dired-do-search'. + +\(fn &optional ARG)" t nil) + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "pdf-occur" '("pdf-occur-"))) + +;;;*** + +;;;### (autoloads nil "pdf-outline" "pdf-outline.el" (0 0 0 0)) +;;; Generated autoloads from pdf-outline.el + +(autoload 'pdf-outline-minor-mode "pdf-outline" "\ +Display an outline of a PDF document. + +If called interactively, enable Pdf-Outline minor mode if ARG is +positive, and disable it if ARG is zero or negative. If called +from Lisp, also enable the mode if ARG is omitted or nil, and +toggle it if ARG is `toggle'; disable the mode otherwise. + +This provides a PDF's outline on the menu bar via imenu. +Additionally the same outline may be viewed in a designated +buffer. + +\\{pdf-outline-minor-mode-map} + +\(fn &optional ARG)" t nil) + +(autoload 'pdf-outline "pdf-outline" "\ +Display an PDF outline of BUFFER. + +BUFFER defaults to the current buffer. Select the outline +buffer, unless NO-SELECT-WINDOW-P is non-nil. + +\(fn &optional BUFFER NO-SELECT-WINDOW-P)" t nil) + +(autoload 'pdf-outline-imenu-enable "pdf-outline" "\ +Enable imenu in the current PDF buffer." t nil) + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "pdf-outline" '("pdf-outline"))) + +;;;*** + +;;;### (autoloads nil "pdf-sync" "pdf-sync.el" (0 0 0 0)) +;;; Generated autoloads from pdf-sync.el + +(autoload 'pdf-sync-minor-mode "pdf-sync" "\ +Correlate a PDF position with the TeX file. +\\<pdf-sync-minor-mode-map> +This works via SyncTeX, which means the TeX sources need to have +been compiled with `--synctex=1'. In AUCTeX this can be done by +setting `TeX-source-correlate-method' to 'synctex (before AUCTeX +is loaded) and enabling `TeX-source-correlate-mode'. + +If called interactively, enable Pdf-Sync minor mode if ARG is +positive, and disable it if ARG is zero or negative. If called +from Lisp, also enable the mode if ARG is omitted or nil, and +toggle it if ARG is `toggle'; disable the mode otherwise. + +Then \\[pdf-sync-backward-search-mouse] in the PDF buffer will open the +corresponding TeX location. + +If AUCTeX is your preferred tex-mode, this library arranges to +bind `pdf-sync-forward-display-pdf-key' (the default is `C-c C-g') +to `pdf-sync-forward-search' in `TeX-source-correlate-map'. This +function displays the PDF page corresponding to the current +position in the TeX buffer. This function only works together +with AUCTeX. + +\(fn &optional ARG)" t nil) + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "pdf-sync" '("pdf-sync-"))) + +;;;*** + +;;;### (autoloads nil "pdf-tools" "pdf-tools.el" (0 0 0 0)) +;;; Generated autoloads from pdf-tools.el + +(defvar pdf-tools-handle-upgrades t "\ +Whether PDF Tools should handle upgrading itself.") + +(custom-autoload 'pdf-tools-handle-upgrades "pdf-tools" t) + +(autoload 'pdf-tools-install "pdf-tools" "\ +Install PDF-Tools in all current and future PDF buffers. + +If the `pdf-info-epdfinfo-program' is not running or does not +appear to be working, attempt to rebuild it. If this build +succeeded, continue with the activation of the package. +Otherwise fail silently, i.e. no error is signaled. + +Build the program (if necessary) without asking first, if +NO-QUERY-P is non-nil. + +Don't attempt to install system packages, if SKIP-DEPENDENCIES-P +is non-nil. + +Do not signal an error in case the build failed, if NO-ERROR-P is +non-nil. + +Attempt to install system packages (even if it is deemed +unnecessary), if FORCE-DEPENDENCIES-P is non-nil. + +Note that SKIP-DEPENDENCIES-P and FORCE-DEPENDENCIES-P are +mutually exclusive. + +Note further, that you can influence the installation directory +by setting `pdf-info-epdfinfo-program' to an appropriate +value (e.g. ~/bin/epdfinfo) before calling this function. + +See `pdf-view-mode' and `pdf-tools-enabled-modes'. + +\(fn &optional NO-QUERY-P SKIP-DEPENDENCIES-P NO-ERROR-P FORCE-DEPENDENCIES-P)" t nil) + +(autoload 'pdf-tools-enable-minor-modes "pdf-tools" "\ +Enable MODES in the current buffer. + +MODES defaults to `pdf-tools-enabled-modes'. + +\(fn &optional MODES)" t nil) + +(autoload 'pdf-tools-help "pdf-tools" nil t nil) + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "pdf-tools" '("pdf-tools-"))) + +;;;*** + +;;;### (autoloads nil "pdf-util" "pdf-util.el" (0 0 0 0)) +;;; Generated autoloads from pdf-util.el + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "pdf-util" '("display-buffer-split-below-and-attach" "pdf-util-"))) + +;;;*** + +;;;### (autoloads nil "pdf-view" "pdf-view.el" (0 0 0 0)) +;;; Generated autoloads from pdf-view.el + +(autoload 'pdf-view-bookmark-jump-handler "pdf-view" "\ +The bookmark handler-function interface for bookmark BMK. + +See also `pdf-view-bookmark-make-record'. + +\(fn BMK)" nil nil) + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "pdf-view" '("pdf-view-"))) + +;;;*** + +;;;### (autoloads nil "pdf-virtual" "pdf-virtual.el" (0 0 0 0)) +;;; Generated autoloads from pdf-virtual.el + +(autoload 'pdf-virtual-edit-mode "pdf-virtual" "\ +Major mode when editing a virtual PDF buffer. + +\(fn)" t nil) + +(autoload 'pdf-virtual-view-mode "pdf-virtual" "\ +Major mode in virtual PDF buffers. + +\(fn)" t nil) + +(defvar pdf-virtual-global-minor-mode nil "\ +Non-nil if Pdf-Virtual-Global minor mode is enabled. +See the `pdf-virtual-global-minor-mode' command +for a description of this minor mode. +Setting this variable directly does not take effect; +either customize it (see the info node `Easy Customization') +or call the function `pdf-virtual-global-minor-mode'.") + +(custom-autoload 'pdf-virtual-global-minor-mode "pdf-virtual" nil) + +(autoload 'pdf-virtual-global-minor-mode "pdf-virtual" "\ +Enable recognition and handling of VPDF files. + +If called interactively, enable Pdf-Virtual-Global minor mode if +ARG is positive, and disable it if ARG is zero or negative. If +called from Lisp, also enable the mode if ARG is omitted or nil, +and toggle it if ARG is `toggle'; disable the mode otherwise. + +\(fn &optional ARG)" t nil) + +(autoload 'pdf-virtual-buffer-create "pdf-virtual" "\ + + +\(fn &optional FILENAMES BUFFER-NAME DISPLAY-P)" t nil) + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "pdf-virtual" '("pdf-virtual-"))) + +;;;*** + +;;;### (autoloads nil nil ("pdf-tools-pkg.el") (0 0 0 0)) + +;;;*** + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; coding: utf-8 +;; End: +;;; pdf-tools-autoloads.el ends here diff --git a/elpa/pdf-tools-20211110.513/pdf-tools-pkg.el b/elpa/pdf-tools-20211110.513/pdf-tools-pkg.el @@ -0,0 +1,14 @@ +(define-package "pdf-tools" "20211110.513" "Support library for PDF documents" + '((emacs "24.3") + (tablist "1.0") + (let-alist "1.0.4")) + :commit "a8847b75d3487d60e27762816bdbdd23b6dc1c11" :authors + '(("Andreas Politz" . "politza@fh-trier.de")) + :maintainer + '("Andreas Politz" . "politza@fh-trier.de") + :keywords + '("files" "multimedia") + :url "http://github.com/vedang/pdf-tools/") +;; Local Variables: +;; no-byte-compile: t +;; End: diff --git a/elpa/pdf-tools-20211110.513/pdf-tools.el b/elpa/pdf-tools-20211110.513/pdf-tools.el @@ -0,0 +1,529 @@ +;;; pdf-tools.el --- Support library for PDF documents -*- lexical-binding:t -*- + +;; Copyright (C) 2013, 2014 Andreas Politz + +;; Author: Andreas Politz <politza@fh-trier.de> +;; URL: http://github.com/vedang/pdf-tools/ +;; Keywords: files, multimedia +;; Package: pdf-tools +;; Version: 1.0 +;; Package-Requires: ((emacs "24.3") (tablist "1.0") (let-alist "1.0.4")) + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: +;; +;; PDF Tools is, among other things, a replacement of DocView for PDF +;; files. The key difference is, that pages are not prerendered by +;; e.g. ghostscript and stored in the file-system, but rather created +;; on-demand and stored in memory. +;; +;; Note: This package is built and tested on GNU/Linux systems. It +;; works on macOS and Windows, but is officially supported only on +;; GNU/Linux systems. This package will not make macOS or Windows +;; specific functionality changes, behaviour on these systems is +;; provided as-is. +;; +;; Note: If you ever update it, you need to restart Emacs afterwards. +;; +;; To activate the package put +;; +;; (pdf-tools-install) +;; +;; somewhere in your .emacs.el . +;; +;; M-x pdf-tools-help RET +;; +;; gives some help on using the package and +;; +;; M-x pdf-tools-customize RET +;; +;; offers some customization options. + +;; Features: +;; +;; * View +;; View PDF documents in a buffer with DocView-like bindings. +;; +;; * Isearch +;; Interactively search PDF documents like any other buffer. (Though +;; there is currently no regexp support.) +;; +;; * Follow links +;; Click on highlighted links, moving to some part of a different +;; page, some external file, a website or any other URI. Links may +;; also be followed by keyboard commands. +;; +;; * Annotations +;; Display and list text and markup annotations (like underline), +;; edit their contents and attributes (e.g. color), move them around, +;; delete them or create new ones and then save the modifications +;; back to the PDF file. +;; +;; * Attachments +;; Save files attached to the PDF-file or list them in a dired buffer. +;; +;; * Outline +;; Use imenu or a special buffer to examine and navigate the PDF's +;; outline. +;; +;; * SyncTeX +;; Jump from a position on a page directly to the TeX source and +;; vice-versa. +;; +;; * Misc +;; + Display PDF's metadata. +;; + Mark a region and kill the text from the PDF. +;; + Search for occurrences of a string. +;; + Keep track of visited pages via a history. + +;;; Code: + +(require 'pdf-view) +(require 'pdf-util) +(require 'pdf-info) +(require 'cus-edit) +(require 'compile) +(require 'cl-lib) +(require 'package) + + + +;; * ================================================================== * +;; * Customizables +;; * ================================================================== * + +(defgroup pdf-tools nil + "Support library for PDF documents." + :group 'data) + +(defgroup pdf-tools-faces nil + "Faces determining the colors used in the pdf-tools package. + +In order to customize dark and light colors use +`pdf-tools-customize-faces', or set `custom-face-default-form' to +'all." + :group 'pdf-tools) + +(defconst pdf-tools-modes + '(pdf-history-minor-mode + pdf-isearch-minor-mode + pdf-links-minor-mode + pdf-misc-minor-mode + pdf-outline-minor-mode + pdf-misc-size-indication-minor-mode + pdf-misc-menu-bar-minor-mode + pdf-annot-minor-mode + pdf-sync-minor-mode + pdf-misc-context-menu-minor-mode + pdf-cache-prefetch-minor-mode + pdf-view-auto-slice-minor-mode + pdf-occur-global-minor-mode + pdf-virtual-global-minor-mode)) + +(defcustom pdf-tools-enabled-modes + '(pdf-history-minor-mode + pdf-isearch-minor-mode + pdf-links-minor-mode + pdf-misc-minor-mode + pdf-outline-minor-mode + pdf-misc-size-indication-minor-mode + pdf-misc-menu-bar-minor-mode + pdf-annot-minor-mode + pdf-sync-minor-mode + pdf-misc-context-menu-minor-mode + pdf-cache-prefetch-minor-mode + pdf-occur-global-minor-mode + ;; pdf-virtual-global-minor-mode + ) + "A list of automatically enabled minor-modes. + +PDF Tools is build as a series of minor-modes. This variable and +the function `pdf-tools-install' merely serve as a convenient +wrapper in order to load these modes in current and newly created +PDF buffers." + :group 'pdf-tools + :type `(set ,@(mapcar (lambda (mode) + `(function-item ,mode)) + pdf-tools-modes))) + +(defcustom pdf-tools-enabled-hook nil + "A hook ran after PDF Tools is enabled in a buffer." + :group 'pdf-tools + :type 'hook) + +(defconst pdf-tools-auto-mode-alist-entry + '("\\.[pP][dD][fF]\\'" . pdf-view-mode) + "The entry to use for `auto-mode-alist'.") + +(defconst pdf-tools-magic-mode-alist-entry + '("%PDF" . pdf-view-mode) + "The entry to use for `magic-mode-alist'.") + +(defun pdf-tools-customize () + "Customize Pdf Tools." + (interactive) + (customize-group 'pdf-tools)) + +(defun pdf-tools-customize-faces () + "Customize PDF Tool's faces." + (interactive) + (let ((buffer (format "*Customize Group: %s*" + (custom-unlispify-tag-name 'pdf-tools-faces)))) + (when (buffer-live-p (get-buffer buffer)) + (with-current-buffer (get-buffer buffer) + (rename-uniquely))) + (customize-group 'pdf-tools-faces) + (with-current-buffer buffer + (set (make-local-variable 'custom-face-default-form) 'all)))) + + +;; * ================================================================== * +;; * Installation +;; * ================================================================== * + +;;;###autoload +(defcustom pdf-tools-handle-upgrades t + "Whether PDF Tools should handle upgrading itself." + :group 'pdf-tools + :type 'boolean) + +(make-obsolete-variable 'pdf-tools-handle-upgrades + "Not used anymore" "0.90") + +(defconst pdf-tools-directory + (or (and load-file-name + (file-name-directory load-file-name)) + default-directory) + "The directory from where this library was first loaded.") + +(defvar pdf-tools-msys2-directory nil) + +(defcustom pdf-tools-installer-os nil + "Specifies which installer to use. + +If nil the installer is chosen automatically. This variable is +useful if you have multiple installers present on your +system (e.g. nix on arch linux)" + :group 'pdf-tools + :type 'string) + +(defun pdf-tools-identify-build-directory (directory) + "Return non-nil, if DIRECTORY appears to contain the epdfinfo source. + +Returns the expanded directory-name of DIRECTORY or nil." + (setq directory (file-name-as-directory + (expand-file-name directory))) + (and (file-exists-p (expand-file-name "autobuild" directory)) + (file-exists-p (expand-file-name "epdfinfo.c" directory)) + directory)) + +(defun pdf-tools-locate-build-directory () + "Attempt to locate a source directory. + +Returns a appropriate directory or nil. See also +`pdf-tools-identify-build-directory'." + (cl-some #'pdf-tools-identify-build-directory + (list default-directory + (expand-file-name "build/server" pdf-tools-directory) + (expand-file-name "server") + (expand-file-name "../server" pdf-tools-directory)))) + +(defun pdf-tools-msys2-directory (&optional noninteractive-p) + "Locate the Msys2 installation directory. + +Ask the user if necessary and NONINTERACTIVE-P is nil. +Returns always nil, unless `system-type' equals windows-nt." + (cl-labels ((if-msys2-directory (directory) + (and (stringp directory) + (file-directory-p directory) + (file-exists-p + (expand-file-name "usr/bin/bash.exe" directory)) + directory))) + (when (eq system-type 'windows-nt) + (setq pdf-tools-msys2-directory + (or pdf-tools-msys2-directory + (cl-some #'if-msys2-directory + (cl-mapcan + (lambda (drive) + (list (format "%c:/msys64" drive) + (format "%c:/msys32" drive))) + (number-sequence ?c ?z))) + (unless (or noninteractive-p + (not (y-or-n-p "Do you have Msys2 installed ? "))) + (if-msys2-directory + (read-directory-name + "Please enter Msys2 installation directory: " nil nil t)))))))) + +(defun pdf-tools-msys2-mingw-bin () + "Return the location of /mingw*/bin." + (when (pdf-tools-msys2-directory) + (let ((arch (intern (car (split-string system-configuration "-" t))))) + (expand-file-name + (format "./mingw%s/bin" (if (eq arch 'x86_64) "64" "32")) + (pdf-tools-msys2-directory))))) + +(defun pdf-tools-find-bourne-shell () + "Locate a usable sh." + (or (and (eq system-type 'windows-nt) + (let* ((directory (pdf-tools-msys2-directory))) + (when directory + (expand-file-name "usr/bin/bash.exe" directory)))) + (executable-find "sh"))) + +(defun pdf-tools-build-server (target-directory + &optional + skip-dependencies-p + force-dependencies-p + callback + build-directory) + "Build the epdfinfo program in the background. + +Install into TARGET-DIRECTORY, which should be a directory. + +If CALLBACK is non-nil, it should be a function. It is called +with the compiled executable as the single argument or nil, if +the build failed. + +Expect sources to be in BUILD-DIRECTORY. If nil, search for it +using `pdf-tools-locate-build-directory'. + +See `pdf-tools-install' for the SKIP-DEPENDENCIES-P and +FORCE-DEPENDENCIES-P arguments. + +Returns the buffer of the compilation process." + + (unless callback (setq callback #'ignore)) + (unless build-directory + (setq build-directory (pdf-tools-locate-build-directory))) + (cl-check-type target-directory file-directory) + (setq target-directory (file-name-as-directory + (expand-file-name target-directory))) + (cl-check-type build-directory (and (not null) file-directory)) + (when (and skip-dependencies-p force-dependencies-p) + (error "Can't simultaneously skip and force dependencies")) + (let* ((compilation-auto-jump-to-first-error nil) + (compilation-scroll-output t) + (shell-file-name (pdf-tools-find-bourne-shell)) + (shell-command-switch "-c") + (process-environment process-environment) + (default-directory build-directory) + (autobuild (shell-quote-argument + (expand-file-name "autobuild" build-directory))) + (msys2-p (equal "bash.exe" (file-name-nondirectory shell-file-name)))) + (unless shell-file-name + (error "No suitable shell found")) + (when msys2-p + (push "BASH_ENV=/etc/profile" process-environment)) + (let ((executable + (expand-file-name + (concat "epdfinfo" (and (eq system-type 'windows-nt) ".exe")) + target-directory)) + (compilation-buffer + (compilation-start + (format "%s -i %s%s%s" + autobuild + (shell-quote-argument target-directory) + (cond + (skip-dependencies-p " -D") + (force-dependencies-p " -d") + (t "")) + (if pdf-tools-installer-os (concat " --os " pdf-tools-installer-os) "")) + t))) + ;; In most cases user-input is required, so select the window. + (if (get-buffer-window compilation-buffer) + (select-window (get-buffer-window compilation-buffer)) + (pop-to-buffer compilation-buffer)) + (with-current-buffer compilation-buffer + (setq-local compilation-error-regexp-alist nil) + (add-hook 'compilation-finish-functions + (lambda (_buffer status) + (funcall callback + (and (equal status "finished\n") + executable))) + nil t) + (current-buffer))))) + + +;; * ================================================================== * +;; * Initialization +;; * ================================================================== * + +;;;###autoload +(defun pdf-tools-install (&optional no-query-p skip-dependencies-p + no-error-p force-dependencies-p) + "Install PDF-Tools in all current and future PDF buffers. + +If the `pdf-info-epdfinfo-program' is not running or does not +appear to be working, attempt to rebuild it. If this build +succeeded, continue with the activation of the package. +Otherwise fail silently, i.e. no error is signaled. + +Build the program (if necessary) without asking first, if +NO-QUERY-P is non-nil. + +Don't attempt to install system packages, if SKIP-DEPENDENCIES-P +is non-nil. + +Do not signal an error in case the build failed, if NO-ERROR-P is +non-nil. + +Attempt to install system packages (even if it is deemed +unnecessary), if FORCE-DEPENDENCIES-P is non-nil. + +Note that SKIP-DEPENDENCIES-P and FORCE-DEPENDENCIES-P are +mutually exclusive. + +Note further, that you can influence the installation directory +by setting `pdf-info-epdfinfo-program' to an appropriate +value (e.g. ~/bin/epdfinfo) before calling this function. + +See `pdf-view-mode' and `pdf-tools-enabled-modes'." + (interactive) + (if (or (pdf-info-running-p) + (ignore-errors (pdf-info-check-epdfinfo) t)) + (pdf-tools-install-noverify) + (let ((target-directory + (or (and (stringp pdf-info-epdfinfo-program) + (file-name-directory + pdf-info-epdfinfo-program)) + pdf-tools-directory))) + (if (or no-query-p + (y-or-n-p "Need to (re)build the epdfinfo program, do it now ?")) + (pdf-tools-build-server + target-directory + skip-dependencies-p + force-dependencies-p + (lambda (executable) + (let ((msg (format + "Building the PDF Tools server %s" + (if executable "succeeded" "failed")))) + (if (not executable) + (funcall (if no-error-p #'message #'error) "%s" msg) + (message "%s" msg) + (setq pdf-info-epdfinfo-program executable) + (let ((pdf-info-restart-process-p t)) + (pdf-tools-install-noverify)))))) + (message "PDF Tools not activated"))))) + +(defun pdf-tools-install-noverify () + "Like `pdf-tools-install', but skip checking `pdf-info-epdfinfo-program'." + (add-to-list 'auto-mode-alist pdf-tools-auto-mode-alist-entry) + (add-to-list 'magic-mode-alist pdf-tools-magic-mode-alist-entry) + ;; FIXME: Generalize this sometime. + (when (memq 'pdf-occur-global-minor-mode + pdf-tools-enabled-modes) + (pdf-occur-global-minor-mode 1)) + (when (memq 'pdf-virtual-global-minor-mode + pdf-tools-enabled-modes) + (pdf-virtual-global-minor-mode 1)) + (add-hook 'pdf-view-mode-hook 'pdf-tools-enable-minor-modes) + (dolist (buf (buffer-list)) + (with-current-buffer buf + (when (and (not (derived-mode-p 'pdf-view-mode)) + (pdf-tools-pdf-buffer-p) + (buffer-file-name)) + (pdf-view-mode))))) + +(defun pdf-tools-uninstall () + "Uninstall PDF-Tools in all current and future PDF buffers." + (interactive) + (pdf-info-quit) + (setq-default auto-mode-alist + (remove pdf-tools-auto-mode-alist-entry auto-mode-alist)) + (setq-default magic-mode-alist + (remove pdf-tools-magic-mode-alist-entry magic-mode-alist)) + (pdf-occur-global-minor-mode -1) + (pdf-virtual-global-minor-mode -1) + (remove-hook 'pdf-view-mode-hook 'pdf-tools-enable-minor-modes) + (dolist (buf (buffer-list)) + (with-current-buffer buf + (when (pdf-util-pdf-buffer-p buf) + (pdf-tools-disable-minor-modes pdf-tools-modes) + (normal-mode))))) + +(defun pdf-tools-pdf-buffer-p (&optional buffer) + "Return non-nil if BUFFER contains a PDF document." + (save-current-buffer + (when buffer (set-buffer buffer)) + (save-excursion + (save-restriction + (widen) + (goto-char 1) + (looking-at "%PDF"))))) + +(defun pdf-tools-assert-pdf-buffer (&optional buffer) + (unless (pdf-tools-pdf-buffer-p buffer) + (error "Buffer does not contain a PDF document"))) + +(defun pdf-tools-set-modes-enabled (enable &optional modes) + (dolist (m (or modes pdf-tools-enabled-modes)) + (let ((enabled-p (and (boundp m) + (symbol-value m)))) + (unless (or (and enabled-p enable) + (and (not enabled-p) (not enable))) + (funcall m (if enable 1 -1)))))) + +;;;###autoload +(defun pdf-tools-enable-minor-modes (&optional modes) + "Enable MODES in the current buffer. + +MODES defaults to `pdf-tools-enabled-modes'." + (interactive) + (pdf-util-assert-pdf-buffer) + (pdf-tools-set-modes-enabled t modes) + (run-hooks 'pdf-tools-enabled-hook)) + +(defun pdf-tools-disable-minor-modes (&optional modes) + "Disable MODES in the current buffer. + +MODES defaults to `pdf-tools-enabled-modes'." + (interactive) + (pdf-tools-set-modes-enabled nil modes)) + +(declare-function pdf-occur-global-minor-mode "pdf-occur.el") +(declare-function pdf-virtual-global-minor-mode "pdf-virtual.el") + +;;;###autoload +(defun pdf-tools-help () + (interactive) + (help-setup-xref (list #'pdf-tools-help) + (called-interactively-p 'interactive)) + (with-help-window (help-buffer) + (princ "PDF Tools Help\n\n") + (princ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n") + (dolist (m (cons 'pdf-view-mode + (sort (copy-sequence pdf-tools-modes) 'string<))) + (princ (format "`%s' is " m)) + (describe-function-1 m) + (terpri) (terpri) + (princ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n")))) + + +;; * ================================================================== * +;; * Debugging +;; * ================================================================== * + +(defvar pdf-tools-debug nil + "Non-nil, if debugging PDF Tools.") + +(defun pdf-tools-toggle-debug () + (interactive) + (setq pdf-tools-debug (not pdf-tools-debug)) + (when (called-interactively-p 'any) + (message "Toggled debugging %s" (if pdf-tools-debug "on" "off")))) + +(provide 'pdf-tools) + +;;; pdf-tools.el ends here diff --git a/elpa/pdf-tools-20211110.513/pdf-tools.elc b/elpa/pdf-tools-20211110.513/pdf-tools.elc Binary files differ. diff --git a/elpa/pdf-tools-20211110.513/pdf-util.el b/elpa/pdf-tools-20211110.513/pdf-util.el @@ -0,0 +1,1360 @@ +;;; pdf-util.el --- PDF Utility functions. -*- lexical-binding: t -*- + +;; Copyright (C) 2013, 2014 Andreas Politz + +;; Author: Andreas Politz <politza@fh-trier.de> +;; Keywords: files, multimedia + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: +;; +;;; Todo: +;; + +;;; Code: + +(require 'pdf-macs) +(require 'cl-lib) +(require 'format-spec) +(require 'faces) + +;; These functions are only used after a PdfView window was asserted, +;; which won't succeed, if pdf-view.el isn't loaded. +(declare-function pdf-view-image-size "pdf-view") +(declare-function pdf-view-image-offset "pdf-view") +(declare-function pdf-cache-pagesize "pdf-cache") +(declare-function pdf-view-image-type "pdf-view") + + + +;; * ================================================================== * +;; * Compatibility with older Emacssen (< 25.1) +;; * ================================================================== * + +;; The with-file-modes macro is only available in recent Emacs +;; versions. +(eval-when-compile + (unless (fboundp 'with-file-modes) + (defmacro with-file-modes (modes &rest body) + "Execute BODY with default file permissions temporarily set to MODES. +MODES is as for `set-default-file-modes'." + (declare (indent 1) (debug t)) + (let ((umask (make-symbol "umask"))) + `(let ((,umask (default-file-modes))) + (unwind-protect + (progn + (set-default-file-modes ,modes) + ,@body) + (set-default-file-modes ,umask))))))) + +(unless (fboundp 'alist-get) ;;25.1 + (defun alist-get (key alist &optional default remove) + "Get the value associated to KEY in ALIST. +DEFAULT is the value to return if KEY is not found in ALIST. +REMOVE, if non-nil, means that when setting this element, we should +remove the entry if the new value is `eql' to DEFAULT." + (ignore remove) ;;Silence byte-compiler. + (let ((x (assq key alist))) + (if x (cdr x) default)))) + +(require 'register) +(unless (fboundp 'register-read-with-preview) + (defalias 'register-read-with-preview 'read-char + "Compatibility alias for pdf-tools.")) + +;; In Emacs 24.3 window-width does not have a PIXELWISE argument. +(defmacro pdf-util-window-pixel-width (&optional window) + "Return the width of WINDOW in pixel." + (if (< (cdr (subr-arity (symbol-function 'window-body-width))) 2) + (let ((window* (make-symbol "window"))) + `(let ((,window* ,window)) + (* (window-body-width ,window*) + (frame-char-width (window-frame ,window*))))) + `(window-body-width ,window t))) + +;; In Emacs 24.3 image-mode-winprops leads to infinite recursion. +(unless (or (> emacs-major-version 24) + (and (= emacs-major-version 24) + (>= emacs-minor-version 4))) + (require 'image-mode) + (defvar image-mode-winprops-original-function + (symbol-function 'image-mode-winprops)) + (defvar image-mode-winprops-alist) + (eval-after-load "image-mode" + '(defun image-mode-winprops (&optional window cleanup) + (if (not (eq major-mode 'pdf-view-mode)) + (funcall image-mode-winprops-original-function + window cleanup) + (cond ((null window) + (setq window + (if (eq (current-buffer) (window-buffer)) (selected-window) t))) + ((eq window t)) + ((not (windowp window)) + (error "Not a window: %s" window))) + (when cleanup + (setq image-mode-winprops-alist + (delq nil (mapcar (lambda (winprop) + (let ((w (car-safe winprop))) + (if (or (not (windowp w)) (window-live-p w)) + winprop))) + image-mode-winprops-alist)))) + (let ((winprops (assq window image-mode-winprops-alist))) + ;; For new windows, set defaults from the latest. + (if winprops + ;; Move window to front. + (setq image-mode-winprops-alist + (cons winprops (delq winprops image-mode-winprops-alist))) + (setq winprops (cons window + (copy-alist (cdar image-mode-winprops-alist)))) + ;; Add winprops before running the hook, to avoid inf-loops if the hook + ;; triggers window-configuration-change-hook. + (setq image-mode-winprops-alist + (cons winprops image-mode-winprops-alist)) + (run-hook-with-args 'image-mode-new-window-functions winprops)) + winprops))))) + + + +;; * ================================================================== * +;; * Transforming coordinates +;; * ================================================================== * + + +(defun pdf-util-scale (list-of-edges-or-pos scale &optional rounding-fn) + "Scale LIST-OF-EDGES-OR-POS by SCALE. + +SCALE is a cons (SX . SY), by which edges/positions are scaled. +If ROUNDING-FN is non-nil, it should be a function of one +argument, a real value, returning a rounded +value (e.g. `ceiling'). + +The elements in LIST-OF-EDGES-OR-POS should be either a list +\(LEFT TOP RIGHT BOT\) or a position \(X . Y\). + +LIST-OF-EDGES-OR-POS may also be a single such element. + +Return scaled list of edges if LIST-OF-EDGES-OR-POS was indeed a list, +else return the scaled singleton." + + (let ((have-list-p (listp (car list-of-edges-or-pos)))) + (unless have-list-p + (setq list-of-edges-or-pos (list list-of-edges-or-pos))) + (let* ((sx (car scale)) + (sy (cdr scale)) + (result + (mapcar + (lambda (edges) + (cond + ((consp (cdr edges)) + (let ((e (list (* (nth 0 edges) sx) + (* (nth 1 edges) sy) + (* (nth 2 edges) sx) + (* (nth 3 edges) sy)))) + (if rounding-fn + (mapcar rounding-fn e) + e))) + (rounding-fn + (cons (funcall rounding-fn (* (car edges) sx)) + (funcall rounding-fn (* (cdr edges) sy)))) + (t + (cons (* (car edges) sx) + (* (cdr edges) sy))))) + list-of-edges-or-pos))) + (if have-list-p + result + (car result))))) + +(defun pdf-util-scale-to (list-of-edges from to &optional rounding-fn) + "Scale LIST-OF-EDGES in FROM basis to TO. + +FROM and TO should both be a cons \(WIDTH . HEIGHT\). See also +`pdf-util-scale'." + + (pdf-util-scale list-of-edges + (cons (/ (float (car to)) + (float (car from))) + (/ (float (cdr to)) + (float (cdr from)))) + rounding-fn)) + +(defun pdf-util-scale-pixel-to-points (list-of-pixel-edges + &optional rounding-fn displayed-p window) + "Scale LIST-OF-PIXEL-EDGES to point values. + +The result depends on the currently displayed page in WINDOW. +See also `pdf-util-scale'." + (pdf-util-assert-pdf-window window) + (pdf-util-scale-to + list-of-pixel-edges + (pdf-view-image-size displayed-p window) + (pdf-cache-pagesize (pdf-view-current-page window)) + rounding-fn)) + +(defun pdf-util-scale-points-to-pixel (list-of-points-edges + &optional rounding-fn displayed-p window) + "Scale LIST-OF-POINTS-EDGES to point values. + +The result depends on the currently displayed page in WINDOW. +See also `pdf-util-scale'." + (pdf-util-assert-pdf-window window) + (pdf-util-scale-to + list-of-points-edges + (pdf-cache-pagesize (pdf-view-current-page window)) + (pdf-view-image-size displayed-p window) + rounding-fn)) + +(defun pdf-util-scale-relative-to-points (list-of-relative-edges + &optional rounding-fn window) + "Scale LIST-OF-RELATIVE-EDGES to point values. + +The result depends on the currently displayed page in WINDOW. +See also `pdf-util-scale'." + (pdf-util-assert-pdf-window window) + (pdf-util-scale-to + list-of-relative-edges + '(1.0 . 1.0) + (pdf-cache-pagesize (pdf-view-current-page window)) + rounding-fn)) + +(defun pdf-util-scale-points-to-relative (list-of-points-edges + &optional rounding-fn window) + "Scale LIST-OF-POINTS-EDGES to relative values. + +See also `pdf-util-scale'." + (pdf-util-assert-pdf-window window) + (pdf-util-scale-to + list-of-points-edges + (pdf-cache-pagesize (pdf-view-current-page window)) + '(1.0 . 1.0) + rounding-fn)) + +(defun pdf-util-scale-pixel-to-relative (list-of-pixel-edges + &optional rounding-fn displayed-p window) + "Scale LIST-OF-PIXEL-EDGES to relative values. + +The result depends on the currently displayed page in WINDOW. +See also `pdf-util-scale'." + (pdf-util-assert-pdf-window window) + (pdf-util-scale-to + list-of-pixel-edges + (pdf-view-image-size displayed-p window) + '(1.0 . 1.0) + rounding-fn)) + + +(defun pdf-util-scale-relative-to-pixel (list-of-relative-edges + &optional rounding-fn displayed-p window) + "Scale LIST-OF-EDGES to match SIZE. + +The result depends on the currently displayed page in WINDOW. +See also `pdf-util-scale'." + (pdf-util-assert-pdf-window window) + (pdf-util-scale-to + list-of-relative-edges + '(1.0 . 1.0) + (pdf-view-image-size displayed-p window) + rounding-fn)) + +(defun pdf-util-translate (list-of-edges-or-pos + offset &optional opposite-direction-p) + "Translate LIST-OF-EDGES-OR-POS by OFFSET + +OFFSET should be a cons \(X . Y\), by which to translate +LIST-OF-EDGES-OR-POS. If OPPOSITE-DIRECTION-P is non-nil +translate by \(-X . -Y\). + +See `pdf-util-scale' for the LIST-OF-EDGES-OR-POS argument." + + (let ((have-list-p (listp (car list-of-edges-or-pos)))) + (unless have-list-p + (setq list-of-edges-or-pos (list list-of-edges-or-pos))) + (let* ((ox (if opposite-direction-p + (- (car offset)) + (car offset))) + (oy (if opposite-direction-p + (- (cdr offset)) + (cdr offset))) + (result + (mapcar + (lambda (edges) + (cond + ((consp (cdr edges)) + (list (+ (nth 0 edges) ox) + (+ (nth 1 edges) oy) + (+ (nth 2 edges) ox) + (+ (nth 3 edges) oy))) + (t + (cons (+ (car edges) ox) + (+ (cdr edges) oy))))) + list-of-edges-or-pos))) + (if have-list-p + result + (car result))))) + +(defun pdf-util-edges-transform (region elts &optional to-region-p) + "Translate ELTS according to REGION. + +ELTS may be one edges list or a position or a list thereof. +Translate each from region coordinates to (0 0 1 1) or the +opposite, if TO-REGION-P is non-nil. All coordinates should be +relative. + +Returns the translated list of elements or the single one +depending on the input." + + (when elts + (let ((have-list-p (consp (car-safe elts)))) + (unless have-list-p + (setq elts (list elts))) + (let ((result + (if (null region) + elts + (mapcar (lambda (edges) + (let ((have-pos-p (numberp (cdr edges)))) + (when have-pos-p + (setq edges (list (car edges) (cdr edges) + (car edges) (cdr edges)))) + (pdf-util-with-edges (edges region) + (let ((newedges + (mapcar (lambda (n) + (min 1.0 (max 0.0 n))) + (if to-region-p + `(,(/ (- edges-left region-left) + region-width) + ,(/ (- edges-top region-top) + region-height) + ,(/ (- edges-right region-left) + region-width) + ,(/ (- edges-bot region-top) + region-height)) + `(,(+ (* edges-left region-width) + region-left) + ,(+ (* edges-top region-height) + region-top) + ,(+ (* edges-right region-width) + region-left) + ,(+ (* edges-bot region-height) + region-top)))))) + (if have-pos-p + (cons (car newedges) (cadr newedges)) + newedges))))) + elts)))) + (if have-list-p + result + (car result)))))) + +(defmacro pdf-util-with-edges (list-of-edges &rest body) + "Provide some convenient macros for the edges in LIST-OF-EDGES. + +LIST-OF-EDGES should be a list of variables \(X ...\), each one +holding a list of edges. Inside BODY the symbols X-left, X-top, +X-right, X-bot, X-width and X-height expand to their respective +values." + + (declare (indent 1) (debug (sexp &rest form))) + (unless (cl-every 'symbolp list-of-edges) + (error "Argument should be a list of symbols")) + (let ((list-of-syms + (mapcar (lambda (edge) + (cons edge (mapcar + (lambda (kind) + (intern (format "%s-%s" edge kind))) + '(left top right bot width height)))) + list-of-edges))) + (macroexpand-all + `(cl-symbol-macrolet + ,(apply 'nconc + (mapcar + (lambda (edge-syms) + (let ((edge (nth 0 edge-syms)) + (syms (cdr edge-syms))) + `((,(pop syms) (nth 0 ,edge)) + (,(pop syms) (nth 1 ,edge)) + (,(pop syms) (nth 2 ,edge)) + (,(pop syms) (nth 3 ,edge)) + (,(pop syms) (- (nth 2 ,edge) + (nth 0 ,edge))) + (,(pop syms) (- (nth 3 ,edge) + (nth 1 ,edge)))))) + list-of-syms)) + ,@body)))) + + +;; * ================================================================== * +;; * Scrolling +;; * ================================================================== * + +(defun pdf-util-image-displayed-edges (&optional window displayed-p) + "Return the visible region of the image in WINDOW. + +Returns a list of pixel edges." + (pdf-util-assert-pdf-window) + (let* ((edges (window-inside-pixel-edges window)) + (isize (pdf-view-image-size displayed-p window)) + (offset (if displayed-p + `(0 . 0) + (pdf-view-image-offset window))) + (hscroll (* (window-hscroll window) + (frame-char-width (window-frame window)))) + (vscroll (window-vscroll window t)) + (x0 (+ hscroll (car offset))) + (y0 (+ vscroll (cdr offset))) + (x1 (min (car isize) + (+ x0 (- (nth 2 edges) (nth 0 edges))))) + (y1 (min (cdr isize) + (+ y0 (- (nth 3 edges) (nth 1 edges)))))) + (mapcar 'round (list x0 y0 x1 y1)))) + +(defun pdf-util-required-hscroll (edges &optional eager-p context-pixel) + "Return the amount of scrolling necessary, to make image EDGES visible. + +Scroll as little as necessary. Unless EAGER-P is non-nil, in +which case scroll as much as possible. + +Keep CONTEXT-PIXEL pixel of the image visible at the bottom and +top of the window. CONTEXT-PIXEL defaults to 0. + +Return the required hscroll in columns or nil, if scrolling is not +needed." + + (pdf-util-assert-pdf-window) + (unless context-pixel + (setq context-pixel 0)) + (let* ((win (window-inside-pixel-edges)) + (image-width (car (pdf-view-image-size t))) + (image-left (* (frame-char-width) + (window-hscroll))) + (edges (pdf-util-translate + edges + (pdf-view-image-offset) t))) + (pdf-util-with-edges (win edges) + (let* ((edges-left (- edges-left context-pixel)) + (edges-right (+ edges-right context-pixel))) + (if (< edges-left image-left) + (round (/ (max 0 (if eager-p + (- edges-right win-width) + edges-left)) + (frame-char-width))) + (if (> (min image-width + edges-right) + (+ image-left win-width)) + (round (/ (min (- image-width win-width) + (if eager-p + edges-left + (- edges-right win-width))) + (frame-char-width))))))))) + +(defun pdf-util-required-vscroll (edges &optional eager-p context-pixel) + "Return the amount of scrolling necessary, to make image EDGES visible. + +Scroll as little as necessary. Unless EAGER-P is non-nil, in +which case scroll as much as possible. + +Keep CONTEXT-PIXEL pixel of the image visible at the bottom and +top of the window. CONTEXT-PIXEL defaults to an equivalent pixel +value of `next-screen-context-lines'. + +Return the required vscroll in pixels or nil, if scrolling is not +needed. + +Note: For versions of emacs before 27 this will return lines instead of +pixels. This is because of a change that occurred to `image-mode' in 27." + (pdf-util-assert-pdf-window) + (let* ((win (window-inside-pixel-edges)) + (image-height (cdr (pdf-view-image-size t))) + (image-top (window-vscroll nil t)) + (edges (pdf-util-translate + edges + (pdf-view-image-offset) t))) + (pdf-util-with-edges (win edges) + (let* ((context-pixel (or context-pixel + (* next-screen-context-lines + (frame-char-height)))) + ;;Be careful not to modify edges. + (edges-top (- edges-top context-pixel)) + (edges-bot (+ edges-bot context-pixel)) + (vscroll + (cond ((< edges-top image-top) + (max 0 (if eager-p + (- edges-bot win-height) + edges-top))) + ((> (min image-height + edges-bot) + (+ image-top win-height)) + (min (- image-height win-height) + (if eager-p + edges-top + (- edges-bot win-height))))))) + + + (when vscroll + (round + ;; `image-set-window-vscroll' changed in version 27 to using + ;; pixels, not lines. + (if (version< emacs-version "27") + (/ vscroll (float (frame-char-height))) + vscroll))))))) + +(defun pdf-util-scroll-to-edges (edges &optional eager-p) + "Scroll window such that image EDGES are visible. + +Scroll as little as necessary. Unless EAGER-P is non-nil, in +which case scroll as much as possible." + + (let ((vscroll (pdf-util-required-vscroll edges eager-p)) + (hscroll (pdf-util-required-hscroll edges eager-p))) + (when vscroll + (image-set-window-vscroll vscroll)) + (when hscroll + (image-set-window-hscroll hscroll)))) + + + +;; * ================================================================== * +;; * Temporary files +;; * ================================================================== * + +(defvar pdf-util--base-directory nil + "Base directory for temporary files.") + +(defvar-local pdf-util--dedicated-directory nil + "The relative name of buffer's dedicated directory.") + +(defun pdf-util-dedicated-directory () + "Return the name of a existing dedicated directory. + +The directory is exclusive to the current buffer. It will be +automatically deleted, if Emacs or the current buffer are +killed." + (with-file-modes #o0700 + (unless (and pdf-util--base-directory + (file-directory-p + pdf-util--base-directory) + (not (file-symlink-p + pdf-util--base-directory))) + (add-hook 'kill-emacs-hook + (lambda nil + (when (and pdf-util--base-directory + (file-directory-p pdf-util--base-directory)) + (delete-directory pdf-util--base-directory t)))) + (setq pdf-util--base-directory + (make-temp-file "pdf-tools-" t))) + (unless (and pdf-util--dedicated-directory + (file-directory-p pdf-util--dedicated-directory) + (not (file-symlink-p + pdf-util--base-directory))) + (let ((temporary-file-directory + pdf-util--base-directory)) + (setq pdf-util--dedicated-directory + (make-temp-file (convert-standard-filename (pdf-util-temp-prefix)) + t)) + (add-hook 'kill-buffer-hook 'pdf-util-delete-dedicated-directory + nil t))) + pdf-util--dedicated-directory)) + +(defun pdf-util-delete-dedicated-directory () + "Delete current buffer's dedicated directory." + (delete-directory (pdf-util-dedicated-directory) t)) + +(defun pdf-util-expand-file-name (name) + "Expand filename against current buffer's dedicated directory." + (expand-file-name name (pdf-util-dedicated-directory))) + +(defun pdf-util-temp-prefix () + "Create a temp-file prefix for the current buffer" + (concat (if buffer-file-name + (file-name-nondirectory buffer-file-name) + (replace-regexp-in-string "[^[:alnum:]]+" "-" (buffer-name))) + "-")) + +(defun pdf-util-make-temp-file (&optional prefix dir-flag suffix) + "Create a temporary file in current buffer's dedicated directory. + +See `make-temp-file' for the arguments." + (let ((temporary-file-directory (pdf-util-dedicated-directory))) + (make-temp-file (convert-standard-filename + (or prefix (pdf-util-temp-prefix))) + dir-flag suffix))) + + +;; * ================================================================== * +;; * Various +;; * ================================================================== * + +(defmacro pdf-util-debug (&rest body) + "Execute BODY only if debugging is enabled." + (declare (indent 0) (debug t)) + `(when (bound-and-true-p pdf-tools-debug) + ,@body)) + +(defun pdf-util-pdf-buffer-p (&optional buffer) + (and (or (null buffer) + (buffer-live-p buffer)) + (save-current-buffer + (and buffer (set-buffer buffer)) + (derived-mode-p 'pdf-view-mode)))) + +(defun pdf-util-assert-pdf-buffer (&optional buffer) + (unless (pdf-util-pdf-buffer-p buffer) + (error "Buffer is not in PDFView mode"))) + +(defun pdf-util-pdf-window-p (&optional window) + (unless (or (null window) + (window-live-p window)) + (signal 'wrong-type-argument (list 'window-live-p window))) + (unless window (setq window (selected-window))) + (and (window-live-p window) + (with-selected-window window + (pdf-util-pdf-buffer-p)))) + +(defun pdf-util-assert-pdf-window (&optional window) + (unless (pdf-util-pdf-window-p window) + (error "Window's buffer is not in PdfView mode"))) + +(defun pdf-util-munch-file (filename &optional multibyte-p) + "Read contents from FILENAME and delete it. + +Return the file's content as a unibyte string, unless MULTIBYTE-P +is non-nil." + (unwind-protect + (with-temp-buffer + (set-buffer-multibyte multibyte-p) + (insert-file-contents-literally filename) + (buffer-substring-no-properties + (point-min) + (point-max))) + (when (and filename + (file-exists-p filename)) + (delete-file filename)))) + +(defun pdf-util-hexcolor (color) + "Return COLOR in hex-format. + +Signal an error, if color is invalid." + (if (string-match "\\`#[[:xdigit:]]\\{6\\}\\'" color) + color + (let ((values (color-values color))) + (unless values + (signal 'wrong-type-argument (list 'color-defined-p color))) + (apply 'format "#%02x%02x%02x" + (mapcar (lambda (c) (lsh c -8)) + values))))) + +(defun pdf-util-highlight-regexp-in-string (regexp string &optional face) + "Highlight all occurrences of REGEXP in STRING using FACE. + +FACE defaults to the `match' face. Returns the new fontified +string." + (with-temp-buffer + (save-excursion (insert string)) + (while (and (not (eobp)) + (re-search-forward regexp nil t)) + (if (= (match-beginning 0) + (match-end 0)) + (forward-char) + (put-text-property + (match-beginning 0) + (point) + 'face (or face 'match)))) + (buffer-string))) + +(defun pdf-util-color-completions () + "Return a fontified list of defined colors." + (let ((color-list (list-colors-duplicates)) + colors) + (dolist (cl color-list) + (dolist (c (reverse cl)) + (push (propertize c 'face `(:background ,c)) + colors))) + (nreverse colors))) + +(defun pdf-util-tooltip-in-window (text x y &optional window) + (let* ((we (window-inside-absolute-pixel-edges window)) + (dx (round (+ x (nth 0 we)))) + (dy (round (+ y (nth 1 we)))) + (tooltip-frame-parameters + `((left . ,dx) + (top . ,dy) + ,@tooltip-frame-parameters))) + (tooltip-show text))) + +(defun pdf-util-tooltip-arrow (image-top &optional timeout) + (pdf-util-assert-pdf-window) + (when (floatp image-top) + (setq image-top + (round (* image-top (cdr (pdf-view-image-size)))))) + (let* (x-gtk-use-system-tooltips ;allow for display property in tooltip + (dx (+ (or (car (window-margins)) 0) + (car (window-fringes)))) + (dy image-top) + (pos (list dx dy dx (+ dy (* 2 (frame-char-height))))) + (vscroll + (pdf-util-required-vscroll pos)) + (tooltip-frame-parameters + `((border-width . 0) + (internal-border-width . 0) + ,@tooltip-frame-parameters)) + (tooltip-hide-delay (or timeout 3))) + (when vscroll + (image-set-window-vscroll vscroll)) + (setq dy (max 0 (- dy + (cdr (pdf-view-image-offset)) + (window-vscroll nil t) + (frame-char-height)))) + (when (overlay-get (pdf-view-current-overlay) 'before-string) + (let* ((e (window-inside-pixel-edges)) + (xw (pdf-util-with-edges (e) e-width))) + (cl-incf dx (/ (- xw (car (pdf-view-image-size t))) 2)))) + (pdf-util-tooltip-in-window + (propertize + " " 'display (propertize + "\u2192" ;;right arrow + 'display '(height 2) + 'face `(:foreground + "orange red" + :background + ,(cond + ((bound-and-true-p pdf-view-midnight-minor-mode) + (cdr pdf-view-midnight-colors)) + ((bound-and-true-p pdf-view-themed-minor-mode) + (face-background 'default nil)) + (t "white"))))) + dx dy))) + +(defvar pdf-util--face-colors-cache (make-hash-table)) + +(defadvice enable-theme (after pdf-util-clear-faces-cache activate) + (clrhash pdf-util--face-colors-cache)) + +(defun pdf-util-face-colors (face &optional dark-p) + "Return both colors of FACE as a cons. + +Look also in inherited faces. If DARK-P is non-nil, return dark +colors, otherwise light." + (let* ((bg (if dark-p 'dark 'light)) + (spec (list (get face 'face-defface-spec) + (get face 'theme-face) + (get face 'customized-face))) + (cached (gethash face pdf-util--face-colors-cache))) + (cl-destructuring-bind (&optional cspec color-alist) + cached + (or (and color-alist + (equal cspec spec) + (cdr (assq bg color-alist))) + (let* ((this-bg (frame-parameter nil 'background-mode)) + (frame-background-mode bg) + (f (and (not (eq bg this-bg)) + (x-create-frame-with-faces '((visibility . nil)))))) + (with-selected-frame (or f (selected-frame)) + (unwind-protect + (let ((colors + (cons (face-attribute face :foreground nil 'default) + (face-attribute face :background nil 'default)))) + (puthash face `(,(mapcar 'copy-sequence spec) + ((,bg . ,colors) ,@color-alist)) + pdf-util--face-colors-cache) + colors) + (when (and f (frame-live-p f)) + (delete-frame f))))))))) + +(defun pdf-util-window-attach (awindow &optional window) + "Attach AWINDOW to WINDOW. + +This has the following effect. Whenever WINDOW, defaulting to +the selected window, stops displaying the buffer it currently +displays (e.g., by switching buffers or because it was deleted) +AWINDOW is deleted." + (unless window (setq window (selected-window))) + (let ((buffer (window-buffer window)) + (hook (make-symbol "window-attach-hook"))) + (fset hook + (lambda () + (when (or (not (window-live-p window)) + (not (eq buffer (window-buffer window)))) + (remove-hook 'window-configuration-change-hook + hook) + ;; Deleting windows inside wcch may cause errors in + ;; windows.el . + (run-with-timer + 0 nil (lambda (win) + (when (and (window-live-p win) + (not (eq win (selected-window)))) + (delete-window win))) + awindow)))) + (add-hook 'window-configuration-change-hook hook))) + +(defun display-buffer-split-below-and-attach (buf alist) + "Display buffer action using `pdf-util-window-attach'." + (let ((window (selected-window)) + (height (cdr (assq 'window-height alist))) + newwin) + (when height + (when (floatp height) + (setq height (round (* height (frame-height))))) + (setq height (- (max height window-min-height)))) + (setq newwin (window--display-buffer + buf + (split-window-below height) + 'window alist)) + (pdf-util-window-attach newwin window) + newwin)) + +(defun pdf-util-goto-position (line &optional column) + "Goto LINE and COLUMN in the current buffer. + +COLUMN defaults to 0. Widen the buffer, if the position is +outside the current limits." + (let ((pos + (when (> line 0) + (save-excursion + (save-restriction + (widen) + (goto-char 1) + (when (= 0 (forward-line (1- line))) + (when (and column (> column 0)) + (forward-char (1- column))) + (point))))))) + (when pos + (when (or (< pos (point-min)) + (> pos (point-max))) + (widen)) + (goto-char pos)))) + +(defun pdf-util-seq-alignment (seq1 seq2 &optional similarity-fn alignment-type) + "Return an alignment of sequences SEQ1 and SEQ2. + +SIMILARITY-FN should be a function. It is called with two +arguments: One element from SEQ1 and one from SEQ2. It should +return a number determining how similar the elements are, where +higher values mean `more similar'. The default returns 1 if the +elements are equal, else -1. + +ALIGNMENT-TYPE may be one of the symbols `prefix', `suffix', +`infix' or nil. If it is `prefix', trailing elements in SEQ2 may +be ignored. For example the alignment of + +\(0 1\) and \(0 1 2\) + +using prefix matching is 0, since the prefixes are equal and the +trailing 2 is ignored. The other possible values have similar +effects. The default is nil, which means to match the whole +sequences. + +Return a cons \(VALUE . ALIGNMENT\), where VALUE says how similar +the sequences are and ALIGNMENT is a list of \(E1 . E2\), where +E1 is an element from SEQ1 or nil, likewise for E2. If one of +them is nil, it means there is gap at this position in the +respective sequence." + + (cl-macrolet ((make-matrix (rows columns) + (list 'apply (list 'quote 'vector) + (list 'cl-loop 'for 'i 'from 1 'to rows + 'collect (list 'make-vector columns nil)))) + (mset (matrix row column newelt) + (list 'aset (list 'aref matrix row) column newelt)) + (mref (matrix row column) + (list 'aref (list 'aref matrix row) column))) + (let* ((nil-value nil) + (len1 (length seq1)) + (len2 (length seq2)) + (d (make-matrix (1+ len1) (1+ len2))) + (prefix-p (memq alignment-type '(prefix infix))) + (suffix-p (memq alignment-type '(suffix infix))) + (similarity-fn (or similarity-fn + (lambda (a b) + (if (equal a b) 1 -1))))) + + (cl-loop for i from 0 to len1 do + (mset d i 0 (- i))) + (cl-loop for j from 0 to len2 do + (mset d 0 j (if suffix-p 0 (- j)))) + + (cl-loop for i from 1 to len1 do + (cl-loop for j from 1 to len2 do + (let ((max (max + (1- (mref d (1- i) j)) + (+ (mref d i (1- j)) + (if (and prefix-p (= i len1)) 0 -1)) + (+ (mref d (1- i) (1- j)) + (funcall similarity-fn + (elt seq1 (1- i)) + (elt seq2 (1- j))))))) + (mset d i j max)))) + + (let ((i len1) + (j len2) + alignment) + (while (or (> i 0) + (> j 0)) + (cond + ((and (> i 0) + (= (mref d i j) + (1- (mref d (1- i) j)))) + (cl-decf i) + (push (cons (elt seq1 i) nil-value) alignment)) + ((and (> j 0) + (= (mref d i j) + (+ (mref d i (1- j)) + (if (or (and (= i 0) suffix-p) + (and (= i len1) prefix-p)) + 0 -1)))) + (cl-decf j) + (push (cons nil-value (elt seq2 j)) alignment)) + (t + (cl-assert (and (> i 0) (> j 0)) t) + (cl-decf i) + (cl-decf j) + (push (cons (elt seq1 i) + (elt seq2 j)) alignment)))) + (cons (mref d len1 len2) alignment))))) + + +(defun pdf-util-pcre-quote (string) + "Escape STRING for use as a PCRE. + +See also `regexp-quote'." + + (let ((to-escape + (eval-when-compile (append "\0\\|()[]{}^$*+?." nil))) + (chars (append string nil)) + escaped) + (dolist (ch chars) + (when (memq ch to-escape) + (push ?\\ escaped)) + (push ch escaped)) + (apply 'string (nreverse escaped)))) + +(defun pdf-util-frame-ppi () + "Return the PPI of the current frame." + (let* ((props (frame-monitor-attributes)) + (px (nthcdr 2 (alist-get 'geometry props))) + (mm (alist-get 'mm-size props)) + (dp (sqrt (+ (expt (nth 0 px) 2) + (expt (nth 1 px) 2)))) + (di (sqrt (+ (expt (/ (nth 0 mm) 25.4) 2) + (expt (/ (nth 1 mm) 25.4) 2))))) + (/ dp di))) + +(defvar pdf-view-use-scaling) + +(defun pdf-util-frame-scale-factor () + "Return the frame scale factor depending on the image type used for display. +When `pdf-view-use-scaling' is non-nil, return the scale factor of the frame +if available. If the scale factor isn't available, return 2 if the +frame's PPI is larger than 180. Otherwise, return 1." + (if pdf-view-use-scaling + (or (and (fboundp 'frame-scale-factor) + (truncate (frame-scale-factor))) + (and (fboundp 'frame-monitor-attributes) + (cdr (assq 'backing-scale-factor (frame-monitor-attributes)))) + (if (>= (pdf-util-frame-ppi) 180) + 2 + 1)) + 1)) + + +;; * ================================================================== * +;; * Imagemagick's convert +;; * ================================================================== * + +(defcustom pdf-util-convert-program + ;; Avoid using the MS Windows command convert.exe . + (unless (memq system-type '(ms-dos windows-nt)) + (executable-find "convert")) + "Absolute path to the convert program." + :group 'pdf-tools + :type 'executable) + +(defcustom pdf-util-fast-image-format nil + "An image format appropriate for fast displaying. + +This should be a cons \(TYPE . EXT\) where type is the Emacs +image-type and EXT the appropriate file extension starting with a +dot. If nil, the value is determined automatically. + +Different formats have different properties, with respect to +Emacs loading time, convert creation time and the file-size. In +general, uncompressed formats are faster, but may need a fair +amount of (temporary) disk space." + :group 'pdf-tools + :type '(cons symbol string)) + +(defun pdf-util-assert-convert-program () + (unless (and pdf-util-convert-program + (file-executable-p pdf-util-convert-program)) + (error "The pdf-util-convert-program is unset or non-executable"))) + +(defun pdf-util-image-file-size (image-file) + "Determine the size of the image in IMAGE-FILE. + +Returns a cons \(WIDTH . HEIGHT\)." + (pdf-util-assert-convert-program) + (with-temp-buffer + (when (save-excursion + (= 0 (call-process + pdf-util-convert-program + nil (current-buffer) nil + image-file "-format" "%w %h" "info:"))) + (let ((standard-input (current-buffer))) + (cons (read) (read)))))) + +(defun pdf-util-convert (in-file out-file &rest spec) + "Convert image IN-FILE to OUT-FILE according to SPEC. + +IN-FILE should be the name of a file containing an image. Write +the result to OUT-FILE. The extension of this filename usually +determines the resulting image-type. + +SPEC is a property list, specifying what the convert program +should do with the image. All manipulations operate on a +rectangle, see below. + +SPEC may contain the following keys, respectively values. + +`:foreground' Set foreground color for all following operations. + +`:background' Dito, for the background color. + +`:commands' A list of strings representing arguments to convert +for image manipulations. It may contain %-escape characters, as +follows. + +%f -- Expands to the foreground color. +%b -- Expands to the background color. +%g -- Expands to the geometry of the current rectangle, i.e. WxH+X+Y. +%x -- Expands to the left edge of rectangle. +%X -- Expands to the right edge of rectangle. +%y -- Expands to the top edge of rectangle. +%Y -- Expands to the bottom edge of rectangle. +%w -- Expands to the width of rectangle. +%h -- Expands to the height of rectangle. + +Keep in mind, that every element of this list is seen by convert +as a single argument. + +`:formats' An alist of additional %-escapes. Every element +should be a cons \(CHAR . STRING\) or \(CHAR . FUNCTION\). In +the first case, all occurrences of %-CHAR in the above commands +will be replaced by STRING. In the second case FUNCTION is +called with the current rectangle and it should return the +replacement string. + +`:apply' A list of rectangles \(\(LEFT TOP RIGHT BOT\) ...\) in +IN-FILE coordinates. Each such rectangle triggers one execution +of the last commands given earlier in SPEC. E.g. a call like + +\(pdf-util-convert + image-file out-file + :foreground \"black\" + :background \"white\" + :commands '\(\"-fill\" \"%f\" \"-draw\" \"rectangle %x,%y,%X,%Y\"\) + :apply '\(\(0 0 10 10\) \(10 10 20 20\)\) + :commands '\(\"-fill\" \"%b\" \"-draw\" \"rectangle %x,%y,%X,%Y\"\) + :apply '\(\(10 0 20 10\) \(0 10 10 20\)\)\) + +would draw a 4x4 checkerboard pattern in the left corner of the +image, while leaving the rest of it as it was. + +Returns OUT-FILE. + +See url `http://www.imagemagick.org/script/convert.php'." + (pdf-util-assert-convert-program) + (let* ((cmds (pdf-util-convert--create-commands spec)) + (status (apply 'call-process + pdf-util-convert-program nil + (get-buffer-create "*pdf-util-convert-output*") + nil + `(,in-file ,@cmds ,out-file)))) + (unless (and (numberp status) (= 0 status)) + (error "The convert program exited with error status: %s" status)) + out-file)) + +(defun pdf-util-convert-asynch (in-file out-file &rest spec-and-callback) + "Like `pdf-util-convert', but asynchronous. + +If the last argument is a function, it is installed as the +process sentinel. + +Returns the convert process." + (pdf-util-assert-convert-program) + (let ((callback (car (last spec-and-callback))) + spec) + (if (functionp callback) + (setq spec (butlast spec-and-callback)) + (setq spec spec-and-callback + callback nil)) + (let* ((cmds (pdf-util-convert--create-commands spec)) + (proc + (apply 'start-process "pdf-util-convert" + (get-buffer-create "*pdf-util-convert-output*") + pdf-util-convert-program + `(,in-file ,@cmds ,out-file)))) + (when callback + (set-process-sentinel proc callback)) + proc))) + +(defun pdf-util-convert-page (&rest specs) + "Convert image of current page according to SPECS. + +Return the converted PNG image as a string. See also +`pdf-util-convert'." + + (pdf-util-assert-pdf-window) + (let ((in-file (make-temp-file "pdf-util-convert" nil ".png")) + (out-file (make-temp-file "pdf-util-convert" nil ".png"))) + (unwind-protect + (let ((image-data + (plist-get (cdr (pdf-view-current-image)) :data))) + (with-temp-file in-file + (set-buffer-multibyte nil) + (set-buffer-file-coding-system 'binary) + (insert image-data)) + (pdf-util-munch-file + (apply 'pdf-util-convert + in-file out-file specs))) + (when (file-exists-p in-file) + (delete-file in-file)) + (when (file-exists-p out-file) + (delete-file out-file))))) + + +(defun pdf-util-convert--create-commands (spec) + (let ((fg "red") + (bg "red") + formats result cmds s) + (while (setq s (pop spec)) + (unless spec + (error "Missing value in convert spec:%s" (cons s spec))) + (cl-case s + (:foreground + (setq fg (pop spec))) + (:background + (setq bg (pop spec))) + (:commands + (setq cmds (pop spec))) + (:formats + (setq formats (append formats (pop spec) nil))) + (:apply + (dolist (m (pop spec)) + (pdf-util-with-edges (m) + (let ((alist (append + (mapcar (lambda (f) + (cons (car f) + (if (stringp (cdr f)) + (cdr f) + (funcall (cdr f) m)))) + formats) + `((?g . ,(format "%dx%d+%d+%d" + m-width m-height + m-left m-top)) + (?x . ,m-left) + (?X . ,m-right) + (?y . ,m-top) + (?Y . ,m-bot) + (?w . ,(- m-right m-left)) + (?h . ,(- m-bot m-top)) + (?f . ,fg) + (?b . ,bg))))) + (dolist (fmt cmds) + (push (format-spec fmt alist) result)))))))) + (nreverse result))) + +;; FIXME: Check code below and document. + +(defun pdf-util-edges-p (obj &optional relative-p) + "Return non-nil, if OBJ look like edges. + +If RELATIVE-P is non-nil, also check that all values <= 1." + + (and (consp obj) + (ignore-errors (= 4 (length obj))) + (cl-every (lambda (x) + (and (numberp x) + (>= x 0) + (or (null relative-p) + (<= x 1)))) + obj))) + +(defun pdf-util-edges-empty-p (edges) + "Return non-nil, if EDGES area is empty." + (pdf-util-with-edges (edges) + (or (<= edges-width 0) + (<= edges-height 0)))) + +(defun pdf-util-edges-inside-p (edges pos &optional epsilon) + (pdf-util-edges-contained-p + edges + (list (car pos) (cdr pos) (car pos) (cdr pos)) + epsilon)) + +(defun pdf-util-edges-contained-p (edges contained &optional epsilon) + (unless epsilon (setq epsilon 0)) + (pdf-util-with-edges (edges contained) + (and (<= (- edges-left epsilon) + contained-left) + (>= (+ edges-right epsilon) + contained-right) + (<= (- edges-top epsilon) + contained-top) + (>= (+ edges-bot epsilon) + contained-bot)))) + +(defun pdf-util-edges-intersection (e1 e2) + (pdf-util-with-edges (edges1 e1 e2) + (let ((left (max e1-left e2-left)) + (top (max e1-top e2-top)) + (right (min e1-right e2-right)) + (bot (min e1-bot e2-bot))) + (when (and (<= left right) + (<= top bot)) + (list left top right bot))))) + +(defun pdf-util-edges-union (&rest edges) + (if (null (cdr edges)) + (car edges) + (list (apply 'min (mapcar 'car edges)) + (apply 'min (mapcar 'cadr edges)) + (apply 'max (mapcar 'cl-caddr edges)) + (apply 'max (mapcar 'cl-cadddr edges))))) + +(defun pdf-util-edges-intersection-area (e1 e2) + (let ((inters (pdf-util-edges-intersection e1 e2))) + (if (null inters) + 0 + (pdf-util-with-edges (inters) + (* inters-width inters-height))))) + +(defun pdf-util-read-image-position (prompt) + "Read a image position using prompt. + +Return the event position object." + (save-selected-window + (let ((ev (pdf-util-read-click-event + (propertize prompt 'face 'minibuffer-prompt))) + (buffer (current-buffer))) + (unless (mouse-event-p ev) + (error "Not a mouse event")) + (let ((posn (event-start ev))) + (unless (and (eq (window-buffer + (posn-window posn)) + buffer) + (eq 'image (car-safe (posn-object posn)))) + (error "Invalid image position")) + posn)))) + +(defun pdf-util-read-click-event (&optional prompt seconds) + (let ((down (read-event prompt seconds))) + (unless (and (mouse-event-p down) + (equal (event-modifiers down) + '(down))) + (error "No a mouse click event")) + (let ((up (read-event prompt seconds))) + (unless (and (mouse-event-p up) + (equal (event-modifiers up) + '(click))) + (error "No a mouse click event")) + up))) + +(defun pdf-util-image-map-mouse-event-proxy (event) + "Set POS-OR-AREA in EVENT to 1 and unread it." + (interactive "e") + (setcar (cdr (cadr event)) 1) + (setq unread-command-events (list event))) + +(defun pdf-util-image-map-divert-mouse-clicks (id &optional buttons) + (dolist (kind '("" "down-" "drag-")) + (dolist (b (or buttons '(2 3 4 5 6))) + (local-set-key + (vector id (intern (format "%smouse-%d" kind b))) + 'pdf-util-image-map-mouse-event-proxy)))) + +(defmacro pdf-util-do-events (event-resolution-unread-p condition &rest body) + "Read EVENTs while CONDITION executing BODY. + +Process at most 1/RESOLUTION events per second. If UNREAD-p is +non-nil, unread the final non-processed event. + +\(FN (EVENT RESOLUTION &optional UNREAD-p) CONDITION &rest BODY\)" + (declare (indent 2) (debug ((symbolp form &optional form) form body))) + (cl-destructuring-bind (event resolution &optional unread-p) + event-resolution-unread-p + (let ((*seconds (make-symbol "seconds")) + (*timestamp (make-symbol "timestamp")) + (*clock (make-symbol "clock")) + (*unread-p (make-symbol "unread-p")) + (*resolution (make-symbol "resolution"))) + `(let* ((,*unread-p ,unread-p) + (,*resolution ,resolution) + (,*seconds 0) + (,*timestamp (float-time)) + (,*clock (lambda (&optional secs) + (when secs + (setq ,*seconds secs + ,*timestamp (float-time))) + (- (+ ,*timestamp ,*seconds) + (float-time)))) + (,event (read-event))) + (while ,condition + (when (<= (funcall ,*clock) 0) + (progn ,@body) + (setq ,event nil) + (funcall ,*clock ,*resolution)) + (setq ,event + (or (read-event nil nil + (and ,event + (max 0 (funcall ,*clock)))) + ,event))) + (when (and ,*unread-p ,event) + (setq unread-command-events + (append unread-command-events + (list ,event)))))))) + +(defmacro pdf-util-track-mouse-dragging (event-resolution &rest body) + "Read mouse movement events executing BODY. + +See also `pdf-util-do-events'. + +This macro should be used inside a command bound to a down-mouse +event. It evaluates to t, if at least one event was processed in +BODY, otherwise nil. In the latter case, the only event (usually +a mouse click event) is unread. + +\(FN (EVENT RESOLUTION) &rest BODY\)" + (declare (indent 1) (debug ((symbolp form) body))) + (let ((ran-once-p (make-symbol "ran-once-p"))) + `(let (,ran-once-p) + (track-mouse + (pdf-util-do-events (,@event-resolution t) + (mouse-movement-p ,(car event-resolution)) + (setq ,ran-once-p t) + ,@body)) + (when (and ,ran-once-p + unread-command-events) + (setq unread-command-events + (butlast unread-command-events))) + ,ran-once-p))) + +(defun pdf-util-remove-duplicates (list) + "Remove duplicates from LIST stably using `equal'." + (let ((ht (make-hash-table :test 'equal)) + result) + (dolist (elt list (nreverse result)) + (unless (gethash elt ht) + (push elt result) + (puthash elt t ht))))) + +(provide 'pdf-util) + +;;; pdf-util.el ends here diff --git a/elpa/pdf-tools-20211110.513/pdf-util.elc b/elpa/pdf-tools-20211110.513/pdf-util.elc Binary files differ. diff --git a/elpa/pdf-tools-20211110.513/pdf-view.el b/elpa/pdf-tools-20211110.513/pdf-view.el @@ -0,0 +1,1684 @@ +;;; pdf-view.el --- View PDF documents. -*- lexical-binding:t -*- + +;; Copyright (C) 2013 Andreas Politz + +;; Author: Andreas Politz <politza@fh-trier.de> +;; Keywords: files, doc-view, pdf + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Functions related to viewing PDF documents. + +;;; Code: + +(require 'image-mode) +(require 'pdf-macs) +(require 'pdf-util) +(require 'pdf-info) +(require 'pdf-cache) +(require 'jka-compr) +(require 'bookmark) +(require 'password-cache) + +(declare-function cua-copy-region "cua-base") + + +;; * ================================================================== * +;; * Customizations +;; * ================================================================== * + +(defgroup pdf-view nil + "View PDF documents." + :group 'pdf-tools) + +(defcustom pdf-view-display-size 'fit-width + "The desired size of displayed pages. + +This may be one of `fit-height', `fit-width', `fit-page' or a +number as a scale factor applied to the document's size. Any +other value behaves like `fit-width'." + :group 'pdf-view + :type '(choice number + (const fit-height) + (const fit-width) + (const fit-page))) + +(make-variable-buffer-local 'pdf-view-display-size) + +(defcustom pdf-view-resize-factor 1.25 + "Fractional amount of resizing of one resize command." + :group 'pdf-view + :type 'number) + +(defcustom pdf-view-continuous t + "In Continuous mode reaching the page edge advances to next/previous page. + +When non-nil, scrolling a line upward at the bottom edge of the page +moves to the next page, and scrolling a line downward at the top edge +of the page moves to the previous page." + :type 'boolean + :group 'pdf-view) + +(defcustom pdf-view-bounding-box-margin 0.05 + "Fractional margin used for slicing with the bounding-box." + :group 'pdf-view + :type 'number) + +(defcustom pdf-view-use-imagemagick nil + "Whether imagemagick should be used for rendering. + +This variable has no effect, if imagemagick was not compiled into +Emacs or if imagemagick is the only way to display PNG images. +FIXME: Explain dis-/advantages of imagemagick and png." + :group 'pdf-view + :type 'boolean) + +(defcustom pdf-view-use-scaling nil + "Whether images should be allowed to be scaled for rendering. + +This variable affects both the reuse of higher-resolution images +as lower-resolution ones by down-scaling the image. As well as +the rendering of higher-resolution for high-resolution displays, +if available." + :group 'pdf-view + :type 'boolean) + +(defface pdf-view-region + '((((background dark)) (:inherit region)) + (((background light)) (:inherit region))) + "Face used to determine the colors of the region." + :group 'pdf-view + :group 'pdf-tools-faces) + +(defface pdf-view-rectangle + '((((background dark)) (:inherit highlight)) + (((background light)) (:inherit highlight))) + "Face used to determine the colors of the highlighted rectangle." + :group 'pdf-view + :group 'pdf-tools-faces) + +(defcustom pdf-view-midnight-colors '("#839496" . "#002b36" ) + "Colors used when command `pdf-view-midnight-minor-mode' is activated. + +This should be a cons \(FOREGROUND . BACKGROUND\) of colors." + :group 'pdf-view + :type '(cons (color :tag "Foreground") + (color :tag "Background"))) + +(defcustom pdf-view-change-page-hook nil + "Hook run after changing to another page, but before displaying it. + +See also `pdf-view-before-change-page-hook' and +`pdf-view-after-change-page-hook'." + :group 'pdf-view + :type 'hook) + +(defcustom pdf-view-before-change-page-hook nil + "Hook run before changing to another page. + +See also `pdf-view-change-page-hook' and +`pdf-view-after-change-page-hook'." + :group 'pdf-view + :type 'hook) + +(defcustom pdf-view-after-change-page-hook nil + "Hook run after changing to and displaying another page. + +See also `pdf-view-change-page-hook' and +`pdf-view-before-change-page-hook'." + :group 'pdf-view + :type 'hook) + +(defcustom pdf-view-use-dedicated-register t + "Whether to use dedicated register for PDF positions. + +If this is non-nil, the commands `pdf-view-position-to-register' +and `pdf-view-jump-to-register' use the buffer-local variable +`pdf-view-register-alist' to store resp. retrieve marked +positions. Otherwise the common variable `register-alist' is +used." + :group 'pdf-view + :type 'boolean) + +(defcustom pdf-view-image-relief 0 + "Add a shadow rectangle around the page's image. + +See :relief property in Info node `(elisp) Image Descriptors'." + :group 'pdf-view + :type '(integer :tag "Pixel") + :link '(info-link "(elisp) Image Descriptors")) + +(defcustom pdf-view-max-image-width 4800 + "Maximum width of any image displayed in pixel." + :group 'pdf-view + :type '(integer :tag "Pixel")) + +(defcustom pdf-view-use-unicode-ligther t + "Decide whether to use unicode symbols in the mode-line. + +On some systems finding a font which supports those symbols can +take some time. If you don't want to spend that time waiting and +don't care for a nicer looking mode-line, set this variable to +nil. + +Note, that this option has only an effect when this library is +loaded." + :group 'pdf-view + :type 'boolean) + +(defcustom pdf-view-incompatible-modes + '(linum-mode linum-relative-mode helm-linum-relative-mode + nlinum-mode nlinum-hl-mode nlinum-relative-mode yalinum-mode) + "A list of modes incompatible with `pdf-view-mode'. + +Issue a warning, if one of them is active in a PDF buffer." + :group 'pdf-view + :type '(repeat symbol)) + + +;; * ================================================================== * +;; * Internal variables and macros +;; * ================================================================== * + +(defvar-local pdf-view-active-region nil + "The active region as a list of edges. + +Edge values are relative coordinates.") + +(defvar-local pdf-view--have-rectangle-region nil + "Non-nil if the region is currently rendered as a rectangle. + +This variable is set in `pdf-view-mouse-set-region' and used in +`pdf-view-mouse-extend-region' to determine the right choice +regarding display of the region in the later function.") + +(defvar-local pdf-view--buffer-file-name nil + "Local copy of remote file or nil.") + +(defvar-local pdf-view--server-file-name nil + "The servers notion of this buffer's filename.") + +(defvar-local pdf-view--next-page-timer nil + "Timer used in `pdf-view-next-page-command'.") + +(defvar-local pdf-view--hotspot-functions nil + "Alist of hotspot functions.") + +(defvar-local pdf-view-register-alist nil + "Local, dedicated register for PDF positions.") + +(defun pdf-view-current-pagelabel (&optional window) + (nth (1- (pdf-view-current-page window)) (pdf-info-pagelabels))) + +(defun pdf-view-active-region-p nil + "Return t if there are active regions." + (not (null pdf-view-active-region))) + +(defmacro pdf-view-assert-active-region () + "Signal an error if there are no active regions." + `(unless (pdf-view-active-region-p) + (error "The region is not active"))) + +(defconst pdf-view-have-image-mode-pixel-vscroll + (>= emacs-major-version 27) + "Whether `image-mode' scrolls vertically by pixels.") + + +;; * ================================================================== * +;; * Major Mode +;; * ================================================================== * + +(defvar pdf-view-mode-map + (let ((map (make-sparse-keymap))) + (set-keymap-parent map image-mode-map) + (define-key map (kbd "Q") 'kill-this-buffer) + ;; Navigation in the document + (define-key map (kbd "n") 'pdf-view-next-page-command) + (define-key map (kbd "p") 'pdf-view-previous-page-command) + (define-key map (kbd "<next>") 'forward-page) + (define-key map (kbd "<prior>") 'backward-page) + (define-key map [remap forward-page] 'pdf-view-next-page-command) + (define-key map [remap backward-page] 'pdf-view-previous-page-command) + (define-key map (kbd "SPC") 'pdf-view-scroll-up-or-next-page) + (define-key map (kbd "S-SPC") 'pdf-view-scroll-down-or-previous-page) + (define-key map (kbd "DEL") 'pdf-view-scroll-down-or-previous-page) + (define-key map (kbd "C-n") 'pdf-view-next-line-or-next-page) + (define-key map (kbd "<down>") 'pdf-view-next-line-or-next-page) + (define-key map [remap next-line] 'pdf-view-next-line-or-next-page) + (define-key map (kbd "C-p") 'pdf-view-previous-line-or-previous-page) + (define-key map (kbd "<up>") 'pdf-view-previous-line-or-previous-page) + (define-key map [remap previous-line] 'pdf-view-previous-line-or-previous-page) + (define-key map (kbd "M-<") 'pdf-view-first-page) + (define-key map [remap beginning-of-buffer] 'pdf-view-first-page) + (define-key map (kbd "M->") 'pdf-view-last-page) + (define-key map [remap end-of-buffer] 'pdf-view-last-page) + (define-key map [remap goto-line] 'pdf-view-goto-page) + (define-key map (kbd "M-g l") 'pdf-view-goto-label) + (define-key map (kbd "RET") 'image-next-line) + ;; Zoom in/out. + (define-key map "+" 'pdf-view-enlarge) + (define-key map "=" 'pdf-view-enlarge) + (define-key map "-" 'pdf-view-shrink) + (define-key map "0" 'pdf-view-scale-reset) + ;; Fit the image to the window + (define-key map "W" 'pdf-view-fit-width-to-window) + (define-key map "H" 'pdf-view-fit-height-to-window) + (define-key map "P" 'pdf-view-fit-page-to-window) + ;; Slicing the image + (define-key map (kbd "s m") 'pdf-view-set-slice-using-mouse) + (define-key map (kbd "s b") 'pdf-view-set-slice-from-bounding-box) + (define-key map (kbd "s r") 'pdf-view-reset-slice) + ;; Reconvert + (define-key map (kbd "C-c C-c") 'doc-view-mode) + (define-key map (kbd "g") 'revert-buffer) + (define-key map (kbd "r") 'revert-buffer) + ;; Region + (define-key map [down-mouse-1] 'pdf-view-mouse-set-region) + (define-key map [M-down-mouse-1] 'pdf-view-mouse-set-region-rectangle) + (define-key map [C-down-mouse-1] 'pdf-view-mouse-extend-region) + (define-key map [remap kill-region] 'pdf-view-kill-ring-save) + (define-key map [remap kill-ring-save] 'pdf-view-kill-ring-save) + (define-key map [remap mark-whole-buffer] 'pdf-view-mark-whole-page) + ;; Other + (define-key map (kbd "C-c C-d") 'pdf-view-dark-minor-mode) + (define-key map (kbd "m") 'pdf-view-position-to-register) + (define-key map (kbd "'") 'pdf-view-jump-to-register) + + (define-key map (kbd "C-c C-i") 'pdf-view-extract-region-image) + ;; Rendering + (define-key map (kbd "C-c C-r m") 'pdf-view-midnight-minor-mode) + (define-key map (kbd "C-c C-r t") 'pdf-view-themed-minor-mode) + (define-key map (kbd "C-c C-r p") 'pdf-view-printer-minor-mode) + map) + "Keymap used by `pdf-view-mode' when displaying a doc as a set of images.") + +(define-derived-mode pdf-view-mode special-mode "PDFView" + "Major mode in PDF buffers. + +PDFView Mode is an Emacs PDF viewer. It displays PDF files as +PNG images in Emacs buffers." + :group 'pdf-view + :abbrev-table nil + :syntax-table nil + ;; Setup a local copy for remote files. + (when (and (or jka-compr-really-do-compress + (let ((file-name-handler-alist nil)) + (not (and buffer-file-name + (file-readable-p buffer-file-name))))) + (pdf-tools-pdf-buffer-p)) + (let ((tempfile (pdf-util-make-temp-file))) + (write-region nil nil tempfile nil 'no-message) + (setq-local pdf-view--buffer-file-name tempfile))) + ;; Decryption needs to be done before any other function calls into + ;; pdf-info.el (e.g. from the mode-line during redisplay during + ;; waiting for process output). + (pdf-view-decrypt-document) + + ;; Setup scroll functions + (if (boundp 'mwheel-scroll-up-function) ; not --without-x build + (setq-local mwheel-scroll-up-function + #'pdf-view-scroll-up-or-next-page)) + (if (boundp 'mwheel-scroll-down-function) + (setq-local mwheel-scroll-down-function + #'pdf-view-scroll-down-or-previous-page)) + + ;; Clearing overlays + (add-hook 'change-major-mode-hook + (lambda () + (remove-overlays (point-min) (point-max) 'pdf-view t)) + nil t) + (remove-overlays (point-min) (point-max) 'pdf-view t) ;Just in case. + + ;; Setup other local variables. + (setq-local mode-line-position + '(" P" (:eval (number-to-string (pdf-view-current-page))) + ;; Avoid errors during redisplay. + "/" (:eval (or (ignore-errors + (number-to-string (pdf-cache-number-of-pages))) + "???")))) + (setq-local auto-hscroll-mode nil) + (setq-local pdf-view--server-file-name (pdf-view-buffer-file-name)) + ;; High values of scroll-conservatively seem to trigger + ;; some display bug in xdisp.c:try_scrolling . + (setq-local scroll-conservatively 0) + (setq-local cursor-type nil) + (setq-local buffer-read-only t) + (setq-local view-read-only nil) + (setq-local bookmark-make-record-function + 'pdf-view-bookmark-make-record) + (setq-local revert-buffer-function #'pdf-view-revert-buffer) + ;; No auto-save at the moment. + (setq-local buffer-auto-save-file-name nil) + ;; Disable image rescaling. + (when (boundp 'image-scaling-factor) + (setq-local image-scaling-factor 1)) + ;; No undo at the moment. + (unless buffer-undo-list + (buffer-disable-undo)) + ;; Enable transient-mark-mode, so region deactivation when quitting + ;; will work. + (setq-local transient-mark-mode t) + ;; In Emacs >= 24.4, `cua-copy-region' should have been advised when + ;; loading pdf-view.el so as to make it work with + ;; pdf-view-mode. Disable cua-mode if that is not the case. + ;; FIXME: cua-mode is a global minor-mode, but setting cua-mode to + ;; nil seems to do the trick. + (when (and (bound-and-true-p cua-mode) + (version< emacs-version "24.4")) + (setq-local cua-mode nil)) + + (add-hook 'window-configuration-change-hook + 'pdf-view-redisplay-some-windows nil t) + (add-hook 'deactivate-mark-hook 'pdf-view-deactivate-region nil t) + (add-hook 'write-contents-functions + 'pdf-view--write-contents-function nil t) + (add-hook 'kill-buffer-hook 'pdf-view-close-document nil t) + (pdf-view-add-hotspot-function + 'pdf-view-text-regions-hotspots-function -9) + + ;; Keep track of display info + (add-hook 'image-mode-new-window-functions + 'pdf-view-new-window-function nil t) + (image-mode-setup-winprops) + + ;; Issue a warning in the future about incompatible modes. + (run-with-timer 1 nil (lambda (buffer) + (when (buffer-live-p buffer) + (pdf-view-check-incompatible-modes buffer))) + (current-buffer))) + +(unless (version< emacs-version "24.4") + (defun cua-copy-region--pdf-view-advice (&rest _) + "If the current buffer is in `pdf-view' mode, call +`pdf-view-kill-ring-save'." + (when (eq major-mode 'pdf-view-mode) + (pdf-view-kill-ring-save) + t)) + + (advice-add 'cua-copy-region + :before-until + #'cua-copy-region--pdf-view-advice)) + +(defun pdf-view-check-incompatible-modes (&optional buffer) + "Check BUFFER for incompatible modes, maybe issue a warning." + (with-current-buffer (or buffer (current-buffer)) + (let ((modes + (cl-remove-if-not + (lambda (mode) (and (symbolp mode) + (boundp mode) + (symbol-value mode))) + pdf-view-incompatible-modes))) + (when modes + (display-warning + 'pdf-view + (format "These modes are incompatible with `pdf-view-mode', + please deactivate them (or customize pdf-view-incompatible-modes): %s" + (mapconcat #'symbol-name modes ","))))))) + +(defun pdf-view-decrypt-document () + "Read a password, if the document is encrypted and open it." + (interactive) + (when (pdf-info-encrypted-p) + (let ((prompt (format "Enter password for `%s': " + (abbreviate-file-name + (buffer-file-name)))) + (key (concat "/pdf-tools" (buffer-file-name))) + (i 3) + password) + (while (and (> i 0) + (pdf-info-encrypted-p)) + (setq i (1- i)) + (setq password (password-read prompt key)) + (setq prompt "Invalid password, try again: ") + (ignore-errors (pdf-info-open nil password))) + (pdf-info-open nil password) + (password-cache-add key password))) + nil) + +(defun pdf-view-buffer-file-name () + "Return the local filename of the PDF in the current buffer. + +This may be different from variable `buffer-file-name' when +operating on a local copy of a remote file." + (or pdf-view--buffer-file-name + (buffer-file-name))) + +(defun pdf-view--write-contents-function () + "Function for `write-contents-functions' to save the buffer." + (when (pdf-util-pdf-buffer-p) + (let ((tempfile (pdf-info-save pdf-view--server-file-name))) + (unwind-protect + (progn + ;; Order matters here: We need to first copy the new + ;; content (tempfile) to the PDF, and then close the PDF. + ;; Since while closing the file (and freeing its resources + ;; in the process), it may be immediately reopened due to + ;; redisplay happening inside the pdf-info-close function + ;; (while waiting for a response from the process.). + (copy-file tempfile (buffer-file-name) t) + (pdf-info-close pdf-view--server-file-name) + + (when pdf-view--buffer-file-name + (copy-file tempfile pdf-view--buffer-file-name t)) + (clear-visited-file-modtime) + (set-buffer-modified-p nil) + (setq pdf-view--server-file-name + (pdf-view-buffer-file-name)) + t) + (when (file-exists-p tempfile) + (delete-file tempfile)))))) + +(defun pdf-view-revert-buffer (&optional ignore-auto noconfirm) + "Revert buffer while preserving current modes. + +Optional parameters IGNORE-AUTO and NOCONFIRM are defined as in +`revert-buffer'." + (interactive (list (not current-prefix-arg))) + ;; Bind to default so that we can use pdf-view-revert-buffer as + ;; revert-buffer-function. A binding of nil is needed in Emacs 24.3, but in + ;; later versions the semantics that nil means the default function should + ;; not relied upon. + (let ((revert-buffer-function (when (fboundp #'revert-buffer--default) + #'revert-buffer--default)) + (after-revert-hook + (cons #'pdf-info-close + after-revert-hook))) + (prog1 + (revert-buffer ignore-auto noconfirm 'preserve-modes) + (pdf-view-redisplay t)))) + +(defun pdf-view-close-document () + "Return immediately after closing document. + +This function always succeeds. See also `pdf-info-close', which +does not return immediately." + (when (pdf-info-running-p) + (let ((pdf-info-asynchronous 'ignore)) + (ignore-errors + (pdf-info-close))))) + + +;; * ================================================================== * +;; * Scaling +;; * ================================================================== * + +(defun pdf-view-fit-page-to-window () + "Fit PDF to window. + +Choose the larger of PDF's height and width, and fits that +dimension to window." + (interactive) + (setq pdf-view-display-size 'fit-page) + (image-set-window-vscroll 0) + (image-set-window-hscroll 0) + (pdf-view-redisplay t)) + +(defun pdf-view-fit-height-to-window () + "Fit PDF height to window." + (interactive) + (setq pdf-view-display-size 'fit-height) + (image-set-window-vscroll 0) + (pdf-view-redisplay t)) + +(defun pdf-view-fit-width-to-window () + "Fit PDF size to window." + (interactive) + (setq pdf-view-display-size 'fit-width) + (image-set-window-hscroll 0) + (pdf-view-redisplay t)) + +(defun pdf-view-enlarge (factor) + "Enlarge PDF by FACTOR. + +When called interactively, uses the value of +`pdf-view-resize-factor'. + +For example, (pdf-view-enlarge 1.25) increases size by 25%." + (interactive + (list (float pdf-view-resize-factor))) + (let* ((size (pdf-view-image-size)) + (pagesize (pdf-cache-pagesize + (pdf-view-current-page))) + (scale (/ (float (car size)) + (float (car pagesize))))) + (setq pdf-view-display-size + (* factor scale)) + (pdf-view-redisplay t))) + +(defun pdf-view-shrink (factor) + "Shrink PDF by FACTOR. + +When called interactively, uses the value of +`pdf-view-resize-factor'. + +For example, (pdf-view-shrink 1.25) decreases size by 20%." + (interactive + (list (float pdf-view-resize-factor))) + (pdf-view-enlarge (/ 1.0 factor))) + +(defun pdf-view-scale-reset () + "Reset PDF to its default set." + (interactive) + (setq pdf-view-display-size 1.0) + (pdf-view-redisplay t)) + + + +;; * ================================================================== * +;; * Moving by pages and scrolling +;; * ================================================================== * + +(defun pdf-view-goto-page (page &optional window) + "Go to PAGE in PDF. + +If optional parameter WINDOW, go to PAGE in all `pdf-view' +windows." + (interactive + (list (if current-prefix-arg + (prefix-numeric-value current-prefix-arg) + (read-number "Page: ")))) + (unless (and (>= page 1) + (<= page (pdf-cache-number-of-pages))) + (error "No such page: %d" page)) + (unless window + (setq window + (if (pdf-util-pdf-window-p) + (selected-window) + t))) + (save-selected-window + ;; Select the window for the hooks below. + (when (window-live-p window) + (select-window window 'norecord)) + (let ((changing-p + (not (eq page (pdf-view-current-page window))))) + (when changing-p + (run-hooks 'pdf-view-before-change-page-hook) + (setf (pdf-view-current-page window) page) + (run-hooks 'pdf-view-change-page-hook)) + (when (window-live-p window) + (pdf-view-redisplay window)) + (when changing-p + (pdf-view-deactivate-region) + (force-mode-line-update) + (run-hooks 'pdf-view-after-change-page-hook)))) + nil) + +(defun pdf-view-next-page (&optional n) + "View the next page in the PDF. + +Optional parameter N moves N pages forward." + (interactive "p") + (pdf-view-goto-page (+ (pdf-view-current-page) + (or n 1)))) + +(defun pdf-view-previous-page (&optional n) + "View the previous page in the PDF. + +Optional parameter N moves N pages backward." + (interactive "p") + (pdf-view-next-page (- (or n 1)))) + +(defun pdf-view-next-page-command (&optional n) + "View the next page in the PDF. + +Optional parameter N moves N pages forward. + +This command is a wrapper for `pdf-view-next-page' that will +indicate to the user if they are on the last page and more." + (declare (interactive-only pdf-view-next-page)) + (interactive "p") + (unless n (setq n 1)) + (when (> (+ (pdf-view-current-page) n) + (pdf-cache-number-of-pages)) + (user-error "Last page")) + (when (< (+ (pdf-view-current-page) n) 1) + (user-error "First page")) + (let ((pdf-view-inhibit-redisplay t)) + (pdf-view-goto-page + (+ (pdf-view-current-page) n))) + (force-mode-line-update) + (sit-for 0) + (when pdf-view--next-page-timer + (cancel-timer pdf-view--next-page-timer) + (setq pdf-view--next-page-timer nil)) + (if (or (not (input-pending-p)) + (and (> n 0) + (= (pdf-view-current-page) + (pdf-cache-number-of-pages))) + (and (< n 0) + (= (pdf-view-current-page) 1))) + (pdf-view-redisplay) + (setq pdf-view--next-page-timer + (run-with-idle-timer 0.001 nil 'pdf-view-redisplay (selected-window))))) + +(defun pdf-view-previous-page-command (&optional n) + "View the previous page in the PDF. + +Optional parameter N moves N pages backward. + +This command is a wrapper for `pdf-view-previous-page'." + (declare (interactive-only pdf-view-previous-page)) + (interactive "p") + (with-no-warnings + (pdf-view-next-page-command (- (or n 1))))) + +(defun pdf-view-first-page () + "View the first page." + (interactive) + (pdf-view-goto-page 1)) + +(defun pdf-view-last-page () + "View the last page." + (interactive) + (pdf-view-goto-page (pdf-cache-number-of-pages))) + +(defun pdf-view-scroll-up-or-next-page (&optional arg) + "Scroll page up ARG lines if possible, else go to the next page. + +When `pdf-view-continuous' is non-nil, scrolling upward at the +bottom edge of the page moves to the next page. Otherwise, go to +next page only on typing SPC (ARG is nil)." + (interactive "P") + (if (or pdf-view-continuous (null arg)) + (let ((hscroll (window-hscroll)) + (cur-page (pdf-view-current-page))) + (when (or (= (window-vscroll nil pdf-view-have-image-mode-pixel-vscroll) + (image-scroll-up arg)) + ;; Workaround rounding/off-by-one issues. + (memq pdf-view-display-size + '(fit-height fit-page))) + (pdf-view-next-page) + (when (/= cur-page (pdf-view-current-page)) + (image-bob) + (image-bol 1)) + (image-set-window-hscroll hscroll))) + (image-scroll-up arg))) + +(defun pdf-view-scroll-down-or-previous-page (&optional arg) + "Scroll page down ARG lines if possible, else go to the previous page. + +When `pdf-view-continuous' is non-nil, scrolling downward at the +top edge of the page moves to the previous page. Otherwise, go +to previous page only on typing DEL (ARG is nil)." + (interactive "P") + (if (or pdf-view-continuous (null arg)) + (let ((hscroll (window-hscroll)) + (cur-page (pdf-view-current-page))) + (when (or (= (window-vscroll nil pdf-view-have-image-mode-pixel-vscroll) + (image-scroll-down arg)) + ;; Workaround rounding/off-by-one issues. + (memq pdf-view-display-size + '(fit-height fit-page))) + (pdf-view-previous-page) + (when (/= cur-page (pdf-view-current-page)) + (image-eob) + (image-bol 1)) + (image-set-window-hscroll hscroll))) + (image-scroll-down arg))) + +(defun pdf-view-next-line-or-next-page (&optional arg) + "Scroll upward by ARG lines if possible, else go to the next page. + +When `pdf-view-continuous' is non-nil, scrolling a line upward +at the bottom edge of the page moves to the next page." + (interactive "p") + (if pdf-view-continuous + (let ((hscroll (window-hscroll)) + (cur-page (pdf-view-current-page))) + (when (= (window-vscroll nil pdf-view-have-image-mode-pixel-vscroll) + (image-next-line arg)) + (pdf-view-next-page) + (when (/= cur-page (pdf-view-current-page)) + (image-bob) + (image-bol 1)) + (image-set-window-hscroll hscroll))) + (image-next-line 1))) + +(defun pdf-view-previous-line-or-previous-page (&optional arg) + "Scroll downward by ARG lines if possible, else go to the previous page. + +When `pdf-view-continuous' is non-nil, scrolling a line downward +at the top edge of the page moves to the previous page." + (interactive "p") + (if pdf-view-continuous + (let ((hscroll (window-hscroll)) + (cur-page (pdf-view-current-page))) + (when (= (window-vscroll nil pdf-view-have-image-mode-pixel-vscroll) + (image-previous-line arg)) + (pdf-view-previous-page) + (when (/= cur-page (pdf-view-current-page)) + (image-eob) + (image-bol 1)) + (image-set-window-hscroll hscroll))) + (image-previous-line arg))) + +(defun pdf-view-goto-label (label) + "Go to the page corresponding to LABEL. + +Usually, the label of a document's page is the same as its +displayed page number." + (interactive + (list (let ((labels (pdf-info-pagelabels))) + (completing-read "Goto label: " labels nil t)))) + (let ((index (cl-position label (pdf-info-pagelabels) :test 'equal))) + (unless index + (error "No such label: %s" label)) + (pdf-view-goto-page (1+ index)))) + + +;; * ================================================================== * +;; * Slicing +;; * ================================================================== * + +(defun pdf-view-set-slice (x y width height &optional window) + ;; TODO: add WINDOW to docstring. + "Set the slice of the pages that should be displayed. + +X, Y, WIDTH and HEIGHT should be relative coordinates, i.e. in +\[0;1\]. To reset the slice use `pdf-view-reset-slice'." + (unless (equal (pdf-view-current-slice window) + (list x y width height)) + (setf (pdf-view-current-slice window) + (mapcar (lambda (v) + (max 0 (min 1 v))) + (list x y width height))) + (pdf-view-redisplay window))) + +(defun pdf-view-set-slice-using-mouse () + "Set the slice of the images that should be displayed. + +Set the slice by pressing `mouse-1' at its top-left corner and +dragging it to its bottom-right corner. See also +`pdf-view-set-slice' and `pdf-view-reset-slice'." + (interactive) + (let ((size (pdf-view-image-size)) + x y w h done) + (while (not done) + (let ((e (read-event + (concat "Press mouse-1 at the top-left corner and " + "drag it to the bottom-right corner!")))) + (when (eq (car e) 'drag-mouse-1) + (setq x (car (posn-object-x-y (event-start e)))) + (setq y (cdr (posn-object-x-y (event-start e)))) + (setq w (- (car (posn-object-x-y (event-end e))) x)) + (setq h (- (cdr (posn-object-x-y (event-end e))) y)) + (setq done t)))) + (apply 'pdf-view-set-slice + (pdf-util-scale + (list x y w h) + (cons (/ 1.0 (float (car size))) + (/ 1.0 (float (cdr size)))))))) + +(defun pdf-view-set-slice-from-bounding-box (&optional window) + ;; TODO: add WINDOW to docstring. + "Set the slice from the page's bounding-box. + +The result is that the margins are almost completely cropped, +much more accurate than could be done manually using +`pdf-view-set-slice-using-mouse'. + +See also `pdf-view-bounding-box-margin'." + (interactive) + (let* ((bb (pdf-cache-boundingbox (pdf-view-current-page window))) + (margin (max 0 (or pdf-view-bounding-box-margin 0))) + (slice (list (- (nth 0 bb) + (/ margin 2.0)) + (- (nth 1 bb) + (/ margin 2.0)) + (+ (- (nth 2 bb) (nth 0 bb)) + margin) + (+ (- (nth 3 bb) (nth 1 bb)) + margin)))) + (apply 'pdf-view-set-slice + (append slice (and window (list window)))))) + +(defun pdf-view-reset-slice (&optional window) + ;; TODO: add WINDOW to doctring. + "Reset the current slice. + +After calling this function the whole page will be visible +again." + (interactive) + (when (pdf-view-current-slice window) + (setf (pdf-view-current-slice window) nil) + (pdf-view-redisplay window)) + nil) + +(define-minor-mode pdf-view-auto-slice-minor-mode + "Automatically slice pages according to their bounding boxes. + +See also `pdf-view-set-slice-from-bounding-box'." + :group 'pdf-view + (pdf-util-assert-pdf-buffer) + (cond + (pdf-view-auto-slice-minor-mode + (dolist (win (get-buffer-window-list nil nil t)) + (when (pdf-util-pdf-window-p win) + (pdf-view-set-slice-from-bounding-box win))) + (add-hook 'pdf-view-change-page-hook + 'pdf-view-set-slice-from-bounding-box nil t)) + (t + (remove-hook 'pdf-view-change-page-hook + 'pdf-view-set-slice-from-bounding-box t)))) + + +;; * ================================================================== * +;; * Display +;; * ================================================================== * + +(defvar pdf-view-inhibit-redisplay nil) +(defvar pdf-view-inhibit-hotspots nil) + +(defun pdf-view-image-type () + "Return the image type that should be used. + +The return value is either `imagemagick' (if available and wanted +or if png is not available) or `png'. + +Signal an error, if neither `imagemagick' nor `png' is available. + +See also `pdf-view-use-imagemagick'." + (cond ((and pdf-view-use-imagemagick + (fboundp 'imagemagick-types)) + 'imagemagick) + ((image-type-available-p 'image-io) + 'image-io) + ((image-type-available-p 'png) + 'png) + ((fboundp 'imagemagick-types) + 'imagemagick) + (t + (error "PNG image supported not compiled into Emacs")))) + +(defmacro pdf-view-create-image (data &rest props) + ;; TODO: add DATA and PROPS to docstring. + "Like `create-image', but with set DATA-P and TYPE arguments." + (declare (indent 1) (debug t)) + (let ((image-data (make-symbol "data"))) + `(let ((,image-data ,data)) + (apply #'create-image ,image-data (pdf-view-image-type) t ,@props + (cl-list* + :relief (or pdf-view-image-relief 0) + (when (and (eq (framep-on-display) 'mac) + (= (pdf-util-frame-scale-factor) 2)) + (list :data-2x ,image-data))))))) + +(defun pdf-view-create-page (page &optional window) + "Create an image of PAGE for display on WINDOW." + (let* ((size (pdf-view-desired-image-size page window)) + (data (pdf-cache-renderpage + page (car size) + (if (not pdf-view-use-scaling) + (car size) + (* 2 (car size))))) + (hotspots (pdf-view-apply-hotspot-functions + window page size))) + (pdf-view-create-image data + :width (car size) + :map hotspots + :pointer 'arrow))) + +(defun pdf-view-image-size (&optional displayed-p window) + ;; TODO: add WINDOW to docstring. + "Return the size in pixel of the current image. + +If DISPLAYED-P is non-nil, return the size of the displayed +image. These values may be different, if slicing is used." + (if displayed-p + (with-selected-window (or window (selected-window)) + (image-display-size + (image-get-display-property) t)) + (image-size (pdf-view-current-image window) t))) + +(defun pdf-view-image-offset (&optional window) + ;; TODO: add WINDOW to docstring. + "Return the offset of the current image. + +It is equal to \(LEFT . TOP\) of the current slice in pixel." + (let* ((slice (pdf-view-current-slice window))) + (cond + (slice + (pdf-util-scale-relative-to-pixel + (cons (nth 0 slice) (nth 1 slice)) + window)) + (t + (cons 0 0))))) + +(defun pdf-view-display-page (page &optional window) + "Display page PAGE in WINDOW." + (setf (pdf-view-window-needs-redisplay window) nil) + (pdf-view-display-image + (pdf-view-create-page page window) + window)) + +(defun pdf-view-display-image (image &optional window inhibit-slice-p) + ;; TODO: write documentation! + (let ((ol (pdf-view-current-overlay window))) + (when (window-live-p (overlay-get ol 'window)) + (let* ((size (image-size image t)) + (slice (if (not inhibit-slice-p) + (pdf-view-current-slice window))) + (displayed-width (floor + (if slice + (* (nth 2 slice) + (car (image-size image))) + (car (image-size image)))))) + (setf (pdf-view-current-image window) image) + (move-overlay ol (point-min) (point-max)) + ;; In case the window is wider than the image, center the image + ;; horizontally. + (overlay-put ol 'before-string + (when (> (window-width window) + displayed-width) + (propertize " " 'display + `(space :align-to + ,(/ (- (window-width window) + displayed-width) 2))))) + (overlay-put ol 'display + (if slice + (list (cons 'slice + (pdf-util-scale slice size 'round)) + image) + image)) + (let* ((win (overlay-get ol 'window)) + (hscroll (image-mode-window-get 'hscroll win)) + (vscroll (image-mode-window-get 'vscroll win))) + ;; Reset scroll settings, in case they were changed. + (if hscroll (set-window-hscroll win hscroll)) + (if vscroll (set-window-vscroll + win vscroll pdf-view-have-image-mode-pixel-vscroll))))))) + +(defun pdf-view-redisplay (&optional window) + "Redisplay page in WINDOW. + +If WINDOW is t, redisplay pages in all windows." + (unless pdf-view-inhibit-redisplay + (if (not (eq t window)) + (pdf-view-display-page + (pdf-view-current-page window) + window) + (dolist (win (get-buffer-window-list nil nil t)) + (pdf-view-display-page + (pdf-view-current-page win) + win)) + (when (consp image-mode-winprops-alist) + (dolist (window (mapcar #'car image-mode-winprops-alist)) + (unless (or (not (window-live-p window)) + (eq (current-buffer) + (window-buffer window))) + (setf (pdf-view-window-needs-redisplay window) t))))) + (force-mode-line-update))) + +(defun pdf-view-redisplay-pages (&rest pages) + "Redisplay PAGES in all windows." + (pdf-util-assert-pdf-buffer) + (dolist (window (get-buffer-window-list nil nil t)) + (when (memq (pdf-view-current-page window) + pages) + (pdf-view-redisplay window)))) + +(defun pdf-view-maybe-redisplay-resized-windows () + "Redisplay some windows needing redisplay." + (unless (or (numberp pdf-view-display-size) + (pdf-view-active-region-p) + (> (minibuffer-depth) 0)) + (dolist (window (get-buffer-window-list nil nil t)) + (let ((stored (pdf-view-current-window-size window)) + (size (cons (window-width window) + (window-height window)))) + (unless (equal size stored) + (setf (pdf-view-current-window-size window) size) + (unless (or (null stored) + (and (eq pdf-view-display-size 'fit-width) + (eq (car size) (car stored))) + (and (eq pdf-view-display-size 'fit-height) + (eq (cdr size) (cdr stored)))) + (pdf-view-redisplay window))))))) + +(defun pdf-view-redisplay-some-windows () + (pdf-view-maybe-redisplay-resized-windows) + (dolist (window (get-buffer-window-list nil nil t)) + (when (pdf-view-window-needs-redisplay window) + (pdf-view-redisplay window)))) + +(defun pdf-view-new-window-function (winprops) + ;; TODO: write documentation! + ;; (message "New window %s for buf %s" (car winprops) (current-buffer)) + (cl-assert (or (eq t (car winprops)) + (eq (window-buffer (car winprops)) (current-buffer)))) + (let ((ol (image-mode-window-get 'overlay winprops))) + (if ol + (progn + (setq ol (copy-overlay ol)) + ;; `ol' might actually be dead. + (move-overlay ol (point-min) (point-max))) + (setq ol (make-overlay (point-min) (point-max) nil t)) + (overlay-put ol 'pdf-view t)) + (overlay-put ol 'window (car winprops)) + (unless (windowp (car winprops)) + ;; It's a pseudo entry. Let's make sure it's not displayed (the + ;; `window' property is only effective if its value is a window). + (cl-assert (eq t (car winprops))) + (delete-overlay ol)) + (image-mode-window-put 'overlay ol winprops) + ;; Clean up some overlays. + (dolist (ov (overlays-in (point-min) (point-max))) + (when (and (windowp (overlay-get ov 'window)) + (not (window-live-p (overlay-get ov 'window)))) + (delete-overlay ov))) + (when (and (windowp (car winprops)) + (null (image-mode-window-get 'image winprops))) + ;; We're not displaying an image yet, so let's do so. This + ;; happens when the buffer is displayed for the first time. + (with-selected-window (car winprops) + (pdf-view-goto-page + (or (image-mode-window-get 'page t) 1)))))) + +(defun pdf-view-desired-image-size (&optional page window) + ;; TODO: write documentation! + (let* ((pagesize (pdf-cache-pagesize + (or page (pdf-view-current-page window)))) + (slice (pdf-view-current-slice window)) + (width-scale (/ (/ (float (pdf-util-window-pixel-width window)) + (or (nth 2 slice) 1.0)) + (float (car pagesize)))) + (height (- (nth 3 (window-inside-pixel-edges window)) + (nth 1 (window-inside-pixel-edges window)) + 1)) + (height-scale (/ (/ (float height) + (or (nth 3 slice) 1.0)) + (float (cdr pagesize)))) + (scale width-scale)) + (if (numberp pdf-view-display-size) + (setq scale (float pdf-view-display-size)) + (cl-case pdf-view-display-size + (fit-page + (setq scale (min height-scale width-scale))) + (fit-height + (setq scale height-scale)) + (t + (setq scale width-scale)))) + (let ((width (floor (* (car pagesize) scale))) + (height (floor (* (cdr pagesize) scale)))) + (when (> width (max 1 (or pdf-view-max-image-width width))) + (setq width pdf-view-max-image-width + height (* height (/ (float pdf-view-max-image-width) width)))) + (cons (max 1 width) (max 1 height))))) + +(defun pdf-view-text-regions-hotspots-function (page size) + "Return a list of hotspots for text regions on PAGE using SIZE. + +This will display a text cursor, when hovering over them." + (local-set-key [pdf-view-text-region t] + 'pdf-util-image-map-mouse-event-proxy) + (mapcar (lambda (region) + (let ((e (pdf-util-scale region size 'round))) + `((rect . ((,(nth 0 e) . ,(nth 1 e)) + . (,(nth 2 e) . ,(nth 3 e)))) + pdf-view-text-region + (pointer text)))) + (pdf-cache-textregions page))) + +(define-minor-mode pdf-view-dark-minor-mode + "Mode for PDF documents with dark background. + +This tells the various modes to use their face's dark colors." + :group 'pdf-view + (pdf-util-assert-pdf-buffer) + ;; FIXME: This should really be run in a hook. + (when (bound-and-true-p pdf-isearch-active-mode) + (with-no-warnings + (pdf-isearch-redisplay) + (pdf-isearch-message + (if pdf-view-dark-minor-mode "dark mode" "light mode"))))) + +(define-minor-mode pdf-view-printer-minor-mode + "Display the PDF as it would be printed." + :group 'pdf-view + :lighter " Prn" + (pdf-util-assert-pdf-buffer) + (let ((enable (lambda () + (pdf-info-setoptions :render/printed t)))) + (cond + (pdf-view-printer-minor-mode + (add-hook 'after-save-hook enable nil t) + (add-hook 'after-revert-hook enable nil t)) + (t + (remove-hook 'after-save-hook enable t) + (remove-hook 'after-revert-hook enable t)))) + (pdf-info-setoptions :render/printed pdf-view-printer-minor-mode) + (pdf-cache-clear-images) + (pdf-view-redisplay t)) + +(define-minor-mode pdf-view-midnight-minor-mode + "Apply a color-filter appropriate for past midnight reading. + +The colors are determined by the variable +`pdf-view-midnight-colors', which see. " + :group 'pdf-view + :lighter " Mid" + (pdf-util-assert-pdf-buffer) + ;; FIXME: Maybe these options should be passed stateless to pdf-info-renderpage ? + (let ((enable (lambda () + (pdf-info-setoptions + :render/foreground (or (car pdf-view-midnight-colors) "black") + :render/background (or (cdr pdf-view-midnight-colors) "white") + :render/usecolors t)))) + (cond + (pdf-view-midnight-minor-mode + (add-hook 'after-save-hook enable nil t) + (add-hook 'after-revert-hook enable nil t) + (funcall enable)) + (t + (remove-hook 'after-save-hook enable t) + (remove-hook 'after-revert-hook enable t) + (pdf-info-setoptions :render/usecolors nil)))) + (pdf-cache-clear-images) + (pdf-view-redisplay t)) + +(defun pdf-view-refresh-themed-buffer (&optional get-theme) + "Refresh the current buffer to activate applied colors. + +When GET-THEME is non-nil, also reset the applied colors to the +current theme's colors." + (pdf-util-assert-pdf-buffer) + (pdf-cache-clear-images) + (when get-theme + (pdf-view-set-theme-background)) + (pdf-view-redisplay t)) + +(defun pdf-view-set-theme-background () + "Set the buffer's color filter to correspond to the current Emacs theme." + (pdf-util-assert-pdf-buffer) + (pdf-info-setoptions + :render/foreground (face-foreground 'default nil) + :render/background (face-background 'default nil) + :render/usecolors t)) + +(define-minor-mode pdf-view-themed-minor-mode + "Synchronize color filter with the present Emacs theme. + +The colors are determined by the `face-foreground' and +`face-background' of the currently active theme." + :group 'pdf-view + :lighter " Thm" + (pdf-util-assert-pdf-buffer) + (cond + (pdf-view-themed-minor-mode + (add-hook 'after-save-hook #'pdf-view-set-theme-background nil t) + (add-hook 'after-revert-hook #'pdf-view-set-theme-background nil t)) + (t + (remove-hook 'after-save-hook #'pdf-view-set-theme-background t) + (remove-hook 'after-revert-hook #'pdf-view-set-theme-background t) + (pdf-info-setoptions :render/usecolors nil))) + (pdf-view-refresh-themed-buffer pdf-view-themed-minor-mode)) + +(when pdf-view-use-unicode-ligther + ;; This check uses an implementation detail, which hopefully gets the + ;; right answer. + (and (fontp (char-displayable-p ?⎙)) + (setcdr (assq 'pdf-view-printer-minor-mode minor-mode-alist) + (list " ⎙" ))) + (and (fontp (char-displayable-p ?🌙)) + (setcdr (assq 'pdf-view-midnight-minor-mode minor-mode-alist) + (list " 🌙" )))) + + +;; * ================================================================== * +;; * Hotspot handling +;; * ================================================================== * + +(defun pdf-view-add-hotspot-function (fn &optional layer) + "Register FN as a hotspot function in the current buffer, using LAYER. + +FN will be called in the PDF buffer with the page-number and the +image size \(WIDTH . HEIGHT\) as arguments. It should return a +list of hotspots applicable to the the :map image-property. + +LAYER determines the order: Functions in a higher LAYER will +supersede hotspots in lower ones." + (push (cons (or layer 0) fn) + pdf-view--hotspot-functions)) + +(defun pdf-view-remove-hotspot-function (fn) + "Unregister FN as a hotspot function in the current buffer." + (setq pdf-view--hotspot-functions + (cl-remove fn pdf-view--hotspot-functions + :key 'cdr))) + +(defun pdf-view-sorted-hotspot-functions () + ;; TODO: write documentation! + (mapcar 'cdr (cl-sort (copy-sequence pdf-view--hotspot-functions) + '> :key 'car))) + +(defun pdf-view-apply-hotspot-functions (window page image-size) + ;; TODO: write documentation! + (unless pdf-view-inhibit-hotspots + (save-selected-window + (when window (select-window window 'norecord)) + (apply 'nconc + (mapcar (lambda (fn) + (funcall fn page image-size)) + (pdf-view-sorted-hotspot-functions)))))) + + +;; * ================================================================== * +;; * Region +;; * ================================================================== * + +(defun pdf-view--push-mark () + ;; TODO: write documentation! + (let (mark-ring) + (push-mark-command nil)) + (setq deactivate-mark nil)) + +(defun pdf-view-active-region (&optional deactivate-p) + "Return the active region, a list of edges. + +Deactivate the region if DEACTIVATE-P is non-nil." + (pdf-view-assert-active-region) + (prog1 + pdf-view-active-region + (when deactivate-p + (pdf-view-deactivate-region)))) + +(defun pdf-view-deactivate-region () + "Deactivate the region." + (interactive) + (when pdf-view-active-region + (setq pdf-view-active-region nil) + (deactivate-mark) + (pdf-view-redisplay t))) + +(defun pdf-view-mouse-set-region (event &optional allow-extend-p + rectangle-p) + "Select a region of text using the mouse with mouse event EVENT. + +Allow for stacking of regions, if ALLOW-EXTEND-P is non-nil. + +Create a rectangular region, if RECTANGLE-P is non-nil. + +Stores the region in `pdf-view-active-region'." + (interactive "@e") + (setq pdf-view--have-rectangle-region rectangle-p) + (unless (and (eventp event) + (mouse-event-p event)) + (signal 'wrong-type-argument (list 'mouse-event-p event))) + (unless (and allow-extend-p + (or (null (get this-command 'pdf-view-region-window)) + (equal (get this-command 'pdf-view-region-window) + (selected-window)))) + (pdf-view-deactivate-region)) + (put this-command 'pdf-view-region-window + (selected-window)) + (let* ((window (selected-window)) + (pos (event-start event)) + (begin-inside-image-p t) + (begin (if (posn-image pos) + (posn-object-x-y pos) + (setq begin-inside-image-p nil) + (posn-x-y pos))) + (abs-begin (posn-x-y pos)) + pdf-view-continuous + region) + (when (pdf-util-track-mouse-dragging (event 0.05) + (let* ((pos (event-start event)) + (end (posn-object-x-y pos)) + (end-inside-image-p + (and (eq window (posn-window pos)) + (posn-image pos)))) + (when (or end-inside-image-p + begin-inside-image-p) + (cond + ((and end-inside-image-p + (not begin-inside-image-p)) + ;; Started selection outside the image, setup begin. + (let* ((xy (posn-x-y pos)) + (dxy (cons (- (car xy) (car begin)) + (- (cdr xy) (cdr begin)))) + (size (pdf-view-image-size t))) + (setq begin (cons (max 0 (min (car size) + (- (car end) (car dxy)))) + (max 0 (min (cdr size) + (- (cdr end) (cdr dxy))))) + ;; Store absolute position for later. + abs-begin (cons (- (car xy) + (- (car end) + (car begin))) + (- (cdr xy) + (- (cdr end) + (cdr begin)))) + begin-inside-image-p t))) + ((and begin-inside-image-p + (not end-inside-image-p)) + ;; Moved outside the image, setup end. + (let* ((xy (posn-x-y pos)) + (dxy (cons (- (car xy) (car abs-begin)) + (- (cdr xy) (cdr abs-begin)))) + (size (pdf-view-image-size t))) + (setq end (cons (max 0 (min (car size) + (+ (car begin) (car dxy)))) + (max 0 (min (cdr size) + (+ (cdr begin) (cdr dxy))))))))) + (let ((iregion (if rectangle-p + (list (min (car begin) (car end)) + (min (cdr begin) (cdr end)) + (max (car begin) (car end)) + (max (cdr begin) (cdr end))) + (list (car begin) (cdr begin) + (car end) (cdr end))))) + (setq region + (pdf-util-scale-pixel-to-relative iregion)) + (pdf-view-display-region + (cons region pdf-view-active-region) + rectangle-p) + (pdf-util-scroll-to-edges iregion))))) + (setq pdf-view-active-region + (append pdf-view-active-region + (list region))) + (pdf-view--push-mark)))) + +(defun pdf-view-mouse-extend-region (event) + "Extend the currently active region with mouse event EVENT." + (interactive "@e") + (pdf-view-mouse-set-region + event t pdf-view--have-rectangle-region)) + +(defun pdf-view-mouse-set-region-rectangle (event) + "Like `pdf-view-mouse-set-region' but displays as a rectangle. + +EVENT is the mouse event. + +This is more useful for commands like +`pdf-view-extract-region-image'." + (interactive "@e") + (pdf-view-mouse-set-region event nil t)) + +(defun pdf-view-display-region (&optional region rectangle-p) + ;; TODO: write documentation! + (unless region + (pdf-view-assert-active-region) + (setq region pdf-view-active-region)) + (let ((colors (pdf-util-face-colors + (if rectangle-p 'pdf-view-rectangle 'pdf-view-region) + (bound-and-true-p pdf-view-dark-minor-mode))) + (page (pdf-view-current-page)) + (width (car (pdf-view-image-size)))) + (pdf-view-display-image + (pdf-view-create-image + (if rectangle-p + (pdf-info-renderpage-highlight + page width nil + `(,(car colors) ,(cdr colors) 0.35 ,@region)) + (pdf-info-renderpage-text-regions + page width nil nil + `(,(car colors) ,(cdr colors) ,@region))) + :width width)))) + +(defun pdf-view-kill-ring-save () + "Copy the region to the `kill-ring'." + (interactive) + (pdf-view-assert-active-region) + (let* ((txt (pdf-view-active-region-text))) + (pdf-view-deactivate-region) + (kill-new (mapconcat 'identity txt "\n")))) + +(defun pdf-view-mark-whole-page () + "Mark the whole page." + (interactive) + (pdf-view-deactivate-region) + (setq pdf-view-active-region + (list (list 0 0 1 1))) + (pdf-view--push-mark) + (pdf-view-display-region)) + +(defun pdf-view-active-region-text () + "Return the text of the active region as a list of strings." + (pdf-view-assert-active-region) + (mapcar + (apply-partially 'pdf-info-gettext (pdf-view-current-page)) + pdf-view-active-region)) + +(defun pdf-view-extract-region-image (regions &optional page size + output-buffer no-display-p) + ;; TODO: what is "resp."? Avoid contractions. + "Create a PNG image of REGIONS. + +REGIONS should have the same form as `pdf-view-active-region', +which see. PAGE and SIZE are the page resp. base-size of the +image from which the image-regions will be created; they default +to `pdf-view-current-page' resp. `pdf-view-image-size'. + +Put the image in OUTPUT-BUFFER, defaulting to \"*PDF region +image*\" and display it, unless NO-DISPLAY-P is non-nil. + +In case of multiple regions, the resulting image is constructed +by joining them horizontally. For this operation (and this only) +the `convert' program is used." + + (interactive + (list (if (pdf-view-active-region-p) + (pdf-view-active-region t) + '((0 0 1 1))))) + (unless page + (setq page (pdf-view-current-page))) + (unless size + (setq size (pdf-view-image-size))) + (unless output-buffer + (setq output-buffer (get-buffer-create "*PDF image*"))) + (let* ((images (mapcar (lambda (edges) + (let ((file (make-temp-file "pdf-view")) + (coding-system-for-write 'binary)) + (write-region + (pdf-info-renderpage + page (car size) + :crop-to edges) + nil file nil 'no-message) + file)) + regions)) + result) + (unwind-protect + (progn + (if (= (length images) 1) + (setq result (car images)) + (setq result (make-temp-file "pdf-view")) + ;; Join the images horizontally with a gap of 10 pixel. + (pdf-util-convert + "-noop" ;; workaround limitations of this function + result + :commands `("(" + ,@images + "-background" "white" + "-splice" "0x10+0+0" + ")" + "-gravity" "Center" + "-append" + "+gravity" + "-chop" "0x10+0+0") + :apply '((0 0 0 0)))) + (with-current-buffer output-buffer + (let ((inhibit-read-only t)) + (erase-buffer)) + (set-buffer-multibyte nil) + (insert-file-contents-literally result) + (image-mode) + (unless no-display-p + (pop-to-buffer (current-buffer))))) + (dolist (f (cons result images)) + (when (file-exists-p f) + (delete-file f)))))) + +;; * ================================================================== * +;; * Bookmark + Register Integration +;; * ================================================================== * + +(defun pdf-view-bookmark-make-record (&optional no-page no-slice no-size no-origin) + ;; TODO: add NO-PAGE, NO-SLICE, NO-SIZE, NO-ORIGIN to the docstring. + "Create a bookmark PDF record. + +The optional, boolean args exclude certain attributes." + (let ((displayed-p (eq (current-buffer) + (window-buffer)))) + (cons (buffer-name) + (append (bookmark-make-record-default nil t 1) + `(,(unless no-page + (cons 'page (pdf-view-current-page))) + ,(unless no-slice + (cons 'slice (and displayed-p + (pdf-view-current-slice)))) + ,(unless no-size + (cons 'size pdf-view-display-size)) + ,(unless no-origin + (cons 'origin + (and displayed-p + (let ((edges (pdf-util-image-displayed-edges nil t))) + (pdf-util-scale-pixel-to-relative + (cons (car edges) (cadr edges)) nil t))))) + (handler . pdf-view-bookmark-jump-handler)))))) + +;;;###autoload +(defun pdf-view-bookmark-jump-handler (bmk) + "The bookmark handler-function interface for bookmark BMK. + +See also `pdf-view-bookmark-make-record'." + (let ((page (bookmark-prop-get bmk 'page)) + (slice (bookmark-prop-get bmk 'slice)) + (size (bookmark-prop-get bmk 'size)) + (origin (bookmark-prop-get bmk 'origin)) + (file (bookmark-prop-get bmk 'filename)) + (show-fn-sym (make-symbol "pdf-view-bookmark-after-jump-hook"))) + (fset show-fn-sym + (lambda () + (remove-hook 'bookmark-after-jump-hook show-fn-sym) + (unless (derived-mode-p 'pdf-view-mode) + (pdf-view-mode)) + (with-selected-window + (or (get-buffer-window (current-buffer) 0) + (selected-window)) + (when size + (setq-local pdf-view-display-size size)) + (when slice + (apply 'pdf-view-set-slice slice)) + (when (numberp page) + (pdf-view-goto-page page)) + (when origin + (let ((size (pdf-view-image-size t))) + (image-set-window-hscroll + (round (/ (* (car origin) (car size)) + (frame-char-width)))) + (image-set-window-vscroll + (round (/ (* (cdr origin) (cdr size)) + (if pdf-view-have-image-mode-pixel-vscroll + 1 + (frame-char-height)))))))))) + (add-hook 'bookmark-after-jump-hook show-fn-sym) + (set-buffer (or (find-buffer-visiting file) + (find-file-noselect file))))) + +(defun pdf-view-bookmark-jump (bmk) + "Switch to bookmark BMK. + +This function is like `bookmark-jump', but it always uses the +selected window for display and does not run any hooks. Also, it +works only with bookmarks created by +`pdf-view-bookmark-make-record'." + + (let* ((file (bookmark-prop-get bmk 'filename)) + (buffer (or (find-buffer-visiting file) + (find-file-noselect file)))) + (switch-to-buffer buffer) + (let (bookmark-after-jump-hook) + (pdf-view-bookmark-jump-handler bmk) + (run-hooks 'bookmark-after-jump-hook)))) + +(defun pdf-view-registerv-make () + "Create a PDF register entry of the current position." + (registerv-make + (pdf-view-bookmark-make-record nil t t) + :print-func 'pdf-view-registerv-print-func + :jump-func 'pdf-view-bookmark-jump + :insert-func (lambda (bmk) + (insert (format "%S" bmk))))) + +(defun pdf-view-registerv-print-func (bmk) + "Print a textual representation of bookmark BMK. + +This function is used as the `:print-func' property with +`registerv-make'." + (let* ((file (bookmark-prop-get bmk 'filename)) + (buffer (find-buffer-visiting file)) + (page (bookmark-prop-get bmk 'page)) + (origin (bookmark-prop-get bmk 'origin))) + (princ (format "PDF position: %s, page %d, %d%%" + (if buffer + (buffer-name buffer) + file) + (or page 1) + (if origin + (round (* 100 (cdr origin))) + 0))))) + +(defmacro pdf-view-with-register-alist (&rest body) + "Setup the proper binding for `register-alist' in BODY. + +This macro may not work as desired when it is nested. See also +`pdf-view-use-dedicated-register'." + (declare (debug t) (indent 0)) + (let ((dedicated-p (make-symbol "dedicated-p"))) + `(let* ((,dedicated-p pdf-view-use-dedicated-register) + (register-alist + (if ,dedicated-p + pdf-view-register-alist + register-alist))) + (unwind-protect + (progn ,@body) + (when ,dedicated-p + (setq pdf-view-register-alist register-alist)))))) + +(defun pdf-view-position-to-register (register) + "Store current PDF position in register REGISTER. + +See also `point-to-register'." + (interactive + (list (pdf-view-with-register-alist + (register-read-with-preview "Position to register: ")))) + (pdf-view-with-register-alist + (set-register register (pdf-view-registerv-make)))) + +(defun pdf-view-jump-to-register (register &optional delete return-register) + ;; TODO: add RETURN-REGISTER to the docstring. + "Move point to a position stored in a REGISTER. + +Optional parameter DELETE is defined as in `jump-to-register'." + (interactive + (pdf-view-with-register-alist + (list + (register-read-with-preview "Jump to register: ") + current-prefix-arg + (and (or pdf-view-use-dedicated-register + (local-variable-p 'register-alist)) + (characterp last-command-event) + last-command-event)))) + (pdf-view-with-register-alist + (let ((return-pos (and return-register + (pdf-view-registerv-make)))) + (jump-to-register register delete) + (when return-register + (set-register return-register return-pos))))) + +(provide 'pdf-view) + +;;; pdf-view.el ends here diff --git a/elpa/pdf-tools-20211110.513/pdf-view.elc b/elpa/pdf-tools-20211110.513/pdf-view.elc Binary files differ. diff --git a/elpa/pdf-tools-20211110.513/pdf-virtual.el b/elpa/pdf-tools-20211110.513/pdf-virtual.el @@ -0,0 +1,1038 @@ +;;; pdf-virtual.el --- Virtual PDF documents -*- lexical-binding: t; -*- + +;; Copyright (C) 2015 Andreas Politz + +;; Author: Andreas Politz <politza@hochschule-trier.de> +;; Keywords: multimedia, files + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; A virtual PDF is a collection of pages, or parts thereof, of +;; arbitrary documents in one particular order. This library acts as +;; an intermediate between pdf-info.el and all other packages, in +;; order to transparently make this collection appear as one single +;; document. +;; +;; The trickiest part is to make these intermediate functions behave +;; like the pdf-info-* equivalents in both the synchronous and +;; asynchronous case. + +;;; Code: +(eval-when-compile + (unless (or (> emacs-major-version 24) + (and (= emacs-major-version 24) + (>= emacs-minor-version 4))) + (error "pdf-virtual.el only works with Emacs >= 24.4"))) + +(require 'let-alist) +(require 'pdf-info) +(require 'pdf-util) + + +;; * ================================================================== * +;; * Variables +;; * ================================================================== * + +(defconst pdf-virtual-magic-mode-regexp "^ *;+ *%VPDF\\_>" + "A regexp matching the first line in a vpdf file.") + +(defvar-local pdf-virtual-document nil + "A list representing the virtual document.") + +(put 'pdf-virtual-document 'permanent-local t) + +(defvar pdf-virtual-adapter-alist nil + "Alist of server functions. + +Each element looks like \(PDF-VIRTUAL-FN . PDF-INFO-FN\). This +list is filled by the macro `pdf-virtual-define-adapter' and used +to enable/disable the corresponding advices.") + + +;; * ================================================================== * +;; * VPDF datastructure +;; * ================================================================== * + +(defun pdf-virtual-pagespec-normalize (page-spec &optional filename) + "Normalize PAGE-SPEC using FILENAME. + +PAGE-SPEC should be as described in +`pdf-virtual-document-create'. FILENAME is used to determine the +last page number, if needed. The `current-buffer', if it is nil. + +Returns a list \(\(FIRST . LAST\) . REGION\)\)." + + (let ((page-spec (cond + ((natnump page-spec) + (list (cons page-spec page-spec))) + ((null (car page-spec)) + (let ((npages (pdf-info-number-of-pages filename))) + (cons (cons 1 npages) + (cdr page-spec)))) + ((natnump (car page-spec)) + (cond + ((natnump (cdr page-spec)) + (list page-spec)) + (t + (cons (cons (car page-spec) + (car page-spec)) + (cdr page-spec))))) + (t page-spec)))) + (when (equal (cdr page-spec) + '(0 0 1 1)) + (setq page-spec `((,(caar page-spec) . ,(cdar page-spec))))) + page-spec)) + +(cl-defstruct pdf-virtual-range + ;; The PDF's filename. + filename + ;; First page in this range. + first + ;; Last page. + last + ;; The edges selected for these pages. + region + ;; The page-index corresponding to the first page in this range. + index-start) + +(cl-defstruct pdf-virtual-document + ;; Array of shared pdf-virtual-range structs, one element for each + ;; page. + page-array + ;; An alist mapping filenames to a list of pages. + file-map) + +(defun pdf-virtual-range-length (page) + "Return the number of pages in PAGE." + (1+ (- (pdf-virtual-range-last page) + (pdf-virtual-range-first page)))) + +(defun pdf-virtual-document-create (list &optional directory + file-error-handler) + "Create a virtual PDF from LIST using DIRECTORY. + +LIST should be a list of elements \(FILENAME . PAGE-SPECS\), +where FILENAME is a PDF document and PAGE-SPECS is a list of +PAGE-RANGE and/or \(PAGE-RANGE . EDGES\). In the later case, +EDGES should be a list of relative coordinates \(LEFT TOP RIGHT +BOT\) selecting a region of the page(s) in PAGE-RANGE. Giving no +PAGE-SPECs at all is equivalent to all pages of FILENAME. + +See `pdf-info-normalize-page-range' for the valid formats of +PAGE-RANGE. +" + + (unless (cl-every 'consp list) + (error "Every element should be a cons: %s" list)) + (unless (cl-every 'stringp (mapcar 'car list)) + (error "The car of every element should be a filename.")) + (unless (cl-every (lambda (elt) + (cl-every (lambda (page) + (or (pdf-info-valid-page-spec-p page) + (and (consp page) + (pdf-info-valid-page-spec-p (car page)) + (pdf-util-edges-p (cdr page) 'relative)))) + elt)) + (mapcar 'cdr list)) + (error + "The cdr of every element should be a list of page-specs")) + (let* ((doc (pdf-virtual-document--normalize + list (or directory default-directory) + file-error-handler)) + (npages 0) + document file-map) + (while doc + (let* ((elt (pop doc)) + (filename (car elt)) + (mapelt (assoc filename file-map)) + (page-specs (cdr elt))) + (if mapelt + (setcdr mapelt (cons (1+ npages) (cdr mapelt))) + (push (list filename (1+ npages)) file-map)) + (while page-specs + (let* ((ps (pop page-specs)) + (first (caar ps)) + (last (cdar ps)) + (region (cdr ps)) + (clx (make-pdf-virtual-range + :filename filename + :first first + :last last + :region region + :index-start npages))) + (cl-incf npages (1+ (- last first))) + (push (make-vector (1+ (- last first)) clx) + document))))) + (make-pdf-virtual-document + :page-array (apply 'vconcat (nreverse document)) + :file-map (nreverse + (mapcar (lambda (f) + (setcdr f (nreverse (cdr f))) + f) + file-map))))) + +(defun pdf-virtual-document--normalize (list &optional directory + file-error-handler) + (unless file-error-handler + (setq file-error-handler + (lambda (filename err) + (signal (car err) + (append (cdr err) (list filename)))))) + (let ((default-directory + (or directory default-directory))) + (setq list (cl-remove-if-not + (lambda (filename) + (condition-case err + (progn + (unless (file-readable-p filename) + (signal 'file-error + (list "File not readable: " filename))) + (pdf-info-open filename) + t) + (error + (funcall file-error-handler filename err) + nil))) + list + :key 'car)) + (let* ((file-attributes (make-hash-table :test 'equal)) + (file-equal-p (lambda (f1 f2) + (let ((a1 (gethash f1 file-attributes)) + (a2 (gethash f2 file-attributes))) + (if (and a1 a2) + (equal a1 a2) + (file-equal-p f1 f2))))) + files normalized) + ;; Optimize file-equal-p by caching file-attributes, which is slow + ;; and would be called quadratic times otherwise. (We don't want + ;; the same file under different names.) + (dolist (f (mapcar 'car list)) + (unless (find-file-name-handler f 'file-equal-p) + (puthash f (file-attributes f) file-attributes))) + (dolist (elt list) + (let ((file (cl-find (car elt) files :test file-equal-p))) + (unless file + (push (car elt) files) + (setq file (car elt))) + (let ((pages (mapcar (lambda (p) + (pdf-virtual-pagespec-normalize p file)) + (or (cdr elt) '(nil)))) + newpages) + (while pages + (let* ((spec (pop pages)) + (first (caar spec)) + (last (cdar spec)) + (region (cdr spec))) + (while (and pages + (eq (1+ last) + (caar (car pages))) + (equal region (cdr (car pages)))) + (setq last (cdar (pop pages)))) + (push `((,first . ,last) . ,region) newpages))) + (push (cons file (nreverse newpages)) + normalized)))) + (nreverse normalized)))) + +(defmacro pdf-virtual-document-defun (name args &optional documentation &rest body) + "Define a PDF Document function. + +Args are just like for `defun'. This macro will ensure, that the +DOCUMENT argument, which should be last, is setup properly in +case it is nil, i.e. check that the buffer passes +`pdf-virtual-buffer-assert-p' and use the variable +`pdf-virtual-document'." + + (declare (doc-string 3) (indent defun) + (debug (&define name lambda-list + [&optional stringp] + def-body))) + (unless (stringp documentation) + (push documentation body) + (setq documentation nil)) + (unless (memq '&optional args) + (setq args (append (butlast args) + (list '&optional) + (last args)))) + (when (memq '&rest args) + (error "&rest argument not supported")) + (let ((doc-arg (car (last args))) + (fn (intern (format "pdf-virtual-document-%s" name)))) + `(progn + (put ',fn 'definition-name ',name) + (defun ,fn + ,args ,documentation + (setq ,doc-arg + (or ,doc-arg + (progn (pdf-virtual-buffer-assert-p) + pdf-virtual-document))) + (cl-check-type ,doc-arg pdf-virtual-document) + ,@body)))) + +(pdf-virtual-document-defun filenames (doc) + "Return the list of filenames in DOC." + (mapcar 'car (pdf-virtual-document-file-map doc))) + +(pdf-virtual-document-defun normalize-pages (pages doc) + "Normalize PAGES using DOC. + +Like `pdf-info-normalize-page-range', except 0 is replaced by +DOC's last page." + + (setq pages (pdf-info-normalize-page-range pages)) + (if (eq 0 (cdr pages)) + `(,(car pages) . ,(pdf-virtual-document-number-of-pages doc)) + pages)) + +(pdf-virtual-document-defun page (page doc) + "Get PAGE of DOC. + +Returns a list \(FILENAME FILE-PAGE REGION\)." + (let ((page (car (pdf-virtual-document-pages (cons page page) doc)))) + (when page + (cl-destructuring-bind (filename first-last region) + page + (list filename (car first-last) region))))) + +(pdf-virtual-document-defun pages (pages doc) + "Get PAGES of DOC. + +PAGES should be a cons \(FIRST . LAST\). Return a list of +ranges corresponding to PAGES. Each element has the form + + \(FILENAME \(FILE-FIRT-PAGE . FILE-LAST-PAGE\) REGION\) +. +" + + (let ((begin (car pages)) + (end (cdr pages))) + (unless (<= begin end) + (error "begin should not exceed end: %s" (cons begin end))) + (let ((arr (pdf-virtual-document-page-array doc)) + result) + (when (or (< begin 1) + (> end (length arr))) + (signal 'args-out-of-range (list 'pages pages))) + (while (<= begin end) + (let* ((page (aref arr (1- begin))) + (filename (pdf-virtual-range-filename page)) + (offset (- (1- begin) + (pdf-virtual-range-index-start page))) + (first (+ (pdf-virtual-range-first page) + offset)) + (last (min (+ first (- end begin)) + (pdf-virtual-range-last page))) + (region (pdf-virtual-range-region page))) + (push `(,filename (,first . ,last) ,region) result) + (cl-incf begin (1+ (- last first))))) + (nreverse result)))) + +(pdf-virtual-document-defun number-of-pages (doc) + "Return the number of pages in DOC." + (length (pdf-virtual-document-page-array doc))) + +(pdf-virtual-document-defun page-of (filename &optional file-page limit doc) + "Return a page number displaying FILENAME's page FILE-PAGE in DOC. + +If FILE-PAGE is nil, return the first page displaying FILENAME. +If LIMIT is non-nil, it should be a range \(FIRST . LAST\) in +which the returned page should fall. This is useful if there are +more than one page displaying FILE-PAGE. LIMIT is ignored, if +FILE-PAGE is nil. + +Return nil if there is no matching page." + + (if (null file-page) + (cadr (assoc filename (pdf-virtual-document-file-map doc))) + (let ((pages (pdf-virtual-document-page-array doc))) + (catch 'found + (mapc + (lambda (pn) + (while (and (<= pn (length pages)) + (equal (pdf-virtual-range-filename (aref pages (1- pn))) + filename)) + (let* ((page (aref pages (1- pn))) + (first (pdf-virtual-range-first page)) + (last (pdf-virtual-range-last page))) + (when (and (>= file-page first) + (<= file-page last)) + (let ((r (+ (pdf-virtual-range-index-start page) + (- file-page (pdf-virtual-range-first page)) + 1))) + (when (or (null limit) + (and (>= r (car limit)) + (<= r (cdr limit)))) + (throw 'found r)))) + (cl-incf pn (1+ (- last first)))))) + (cdr (assoc filename (pdf-virtual-document-file-map doc)))) + nil)))) + +(pdf-virtual-document-defun find-matching-page (page predicate + &optional + backward-p doc) + (unless (and (>= page 1) + (<= page (length (pdf-virtual-document-page-array doc)))) + (signal 'args-out-of-range (list 'page page))) + (let* ((pages (pdf-virtual-document-page-array doc)) + (i (1- page)) + (this (aref pages i)) + other) + (while (and (< i (length pages)) + (>= i 0) + (null other)) + (setq i + (if backward-p + (1- (pdf-virtual-range-index-start this)) + (+ (pdf-virtual-range-length this) + (pdf-virtual-range-index-start this)))) + (when (and (< i (length pages)) + (>= i 0)) + (setq other (aref pages i)) + (unless (funcall predicate this other) + (setq other nil)))) + other)) + +(pdf-virtual-document-defun next-matching-page (page predicate doc) + (pdf-virtual-document-find-matching-page page predicate nil doc)) + +(pdf-virtual-document-defun previous-matching-page (page predicate doc) + (declare (indent 1)) + (pdf-virtual-document-find-matching-page page predicate t doc)) + +(pdf-virtual-document-defun next-file (page doc) + "Return the next page displaying a different file than PAGE. + +PAGE should be a page-number." + (let ((page (pdf-virtual-document-next-matching-page + page + (lambda (this other) + (not (equal (pdf-virtual-range-filename this) + (pdf-virtual-range-filename other))))))) + (when page + (1+ (pdf-virtual-range-index-start page))))) + +(pdf-virtual-document-defun previous-file (page doc) + "Return the previous page displaying a different file than PAGE. + +PAGE should be a page-number." + (let ((page (pdf-virtual-document-previous-matching-page + page + (lambda (this other) + (not (equal (pdf-virtual-range-filename this) + (pdf-virtual-range-filename other))))))) + (when page + (1+ (pdf-virtual-range-index-start page))))) + + +;; * ================================================================== * +;; * Modes +;; * ================================================================== * + +(defvar pdf-virtual-edit-mode-map + (let ((map (make-sparse-keymap))) + (set-keymap-parent map emacs-lisp-mode-map) + (define-key map (kbd "C-c C-c") 'pdf-virtual-view-mode) + map)) + + +;;;###autoload +(define-derived-mode pdf-virtual-edit-mode emacs-lisp-mode "VPDF-Edit" + "Major mode when editing a virtual PDF buffer." + (buffer-enable-undo) + (setq-local buffer-read-only nil) + (unless noninteractive + (message (substitute-command-keys "Press \\[pdf-virtual-view-mode] to view.")))) + +;; FIXME: Provide filename/region from-windows-gathering functions. +(defvar pdf-virtual-view-mode-map + (let ((map (make-sparse-keymap))) + (set-keymap-parent map pdf-view-mode-map) + (define-key map (kbd "C-c C-c") 'pdf-virtual-edit-mode) + (define-key map [remap backward-paragraph] 'pdf-virtual-buffer-backward-file) + (define-key map [remap forward-paragraph] 'pdf-virtual-buffer-forward-file) + (define-key map (kbd "C-c C-c") 'pdf-virtual-edit-mode) + map)) + +;;;###autoload +(define-derived-mode pdf-virtual-view-mode pdf-view-mode "VPDF-View" + "Major mode in virtual PDF buffers." + (setq-local write-contents-functions nil) + (remove-hook 'kill-buffer-hook 'pdf-view-close-document t) + (setq-local header-line-format + `(:eval (pdf-virtual-buffer-current-file))) + (unless noninteractive + (message (substitute-command-keys "Press \\[pdf-virtual-edit-mode] to edit.")))) + +;;;###autoload +(define-minor-mode pdf-virtual-global-minor-mode + "Enable recognition and handling of VPDF files." + :global t + :group 'pdf-tools + (let ((elt `(,pdf-virtual-magic-mode-regexp . pdf-virtual-view-mode))) + (cond + (pdf-virtual-global-minor-mode + (add-to-list 'magic-mode-alist elt)) + (t + (setq magic-mode-alist + (remove elt magic-mode-alist)))) + (dolist (elt pdf-virtual-adapter-alist) + (let ((fn (car elt)) + (orig (cdr elt))) + (advice-remove orig fn) + (when pdf-virtual-global-minor-mode + (advice-add orig :around fn)))))) + +(advice-add 'pdf-virtual-view-mode + :around 'pdf-virtual-view-mode-prepare) + +;; This needs to run before pdf-view-mode does its thing. +(defun pdf-virtual-view-mode-prepare (fn) + (let (list unreadable) + (save-excursion + (goto-char 1) + (unless (looking-at pdf-virtual-magic-mode-regexp) + (pdf-virtual-buffer-assert-p)) + (setq list (read (current-buffer)))) + (setq pdf-virtual-document + (pdf-virtual-document-create + list + nil + (lambda (filename _error) + (push filename unreadable)))) + (when unreadable + (display-warning + 'pdf-virtual + (format "Some documents could not be opened:\n%s" + (mapconcat (lambda (f) + (concat " " f)) + unreadable "\n")))) + (if (= (pdf-virtual-document-number-of-pages) 0) + (error "Document is empty.") + (unless pdf-virtual-global-minor-mode + (pdf-virtual-global-minor-mode 1)) + (funcall fn)))) + + +;; * ================================================================== * +;; * Buffer handling +;; * ================================================================== * + +;;;###autoload +(defun pdf-virtual-buffer-create (&optional filenames buffer-name display-p) + (interactive + (list (directory-files default-directory nil "\\.pdf\\'") + (read-string + "Buffer name (default: all.vpdf): " nil nil "all.vpdf") t)) + (with-current-buffer (generate-new-buffer buffer-name) + (insert ";; %VPDF 1.0\n\n") + (insert ";; File Format +;; +;; FORMAT ::= ( FILES* ) +;; FILES ::= ( FILE . PAGE-SPEC* ) +;; PAGE-SPEC ::= PAGE | ( PAGE . REGION ) +;; PAGE ::= NUMBER | ( FIRST . LAST ) +;; REGION ::= ( LEFT TOP RIGHT BOT ) +;; +;; 0 <= X <= 1, forall X in REGION . + +") + (if (null filenames) + (insert "nil\n") + (insert "(") + (dolist (f filenames) + (insert (format "(%S)\n " f))) + (delete-char -2) + (insert ")\n")) + (pdf-virtual-edit-mode) + (when display-p + (pop-to-buffer (current-buffer))) + (current-buffer))) + +(defun pdf-virtual-buffer-p (&optional buffer) + (save-current-buffer + (when buffer (set-buffer buffer)) + (or (derived-mode-p 'pdf-virtual-view-mode 'pdf-virtual-edit-mode) + pdf-virtual-document))) + +(defun pdf-virtual-view-window-p (&optional window) + (save-selected-window + (when window (select-window window 'norecord)) + (derived-mode-p 'pdf-virtual-view-mode))) + +(defun pdf-virtual-filename-p (filename) + (and (stringp filename) + (file-exists-p filename) + (with-temp-buffer + (save-excursion (insert-file-contents filename nil 0 128)) + (looking-at pdf-virtual-magic-mode-regexp)))) + +(defun pdf-virtual-buffer-assert-p (&optional buffer) + (unless (pdf-virtual-buffer-p buffer) + (error "Buffer is not a virtual PDF buffer"))) + +(defun pdf-virtual-view-window-assert-p (&optional window) + (unless (pdf-virtual-view-window-p window) + (error "Window's buffer is not in `pdf-virtual-view-mode'."))) + +(defun pdf-virtual-buffer-current-file (&optional window) + (pdf-virtual-view-window-assert-p window) + (pdf-virtual-range-filename + (aref (pdf-virtual-document-page-array + pdf-virtual-document) + (1- (pdf-view-current-page window))))) + +(defun pdf-virtual-buffer-forward-file (&optional n interactive-p) + (interactive "p\np") + (pdf-virtual-view-window-assert-p) + (let* ((pn (pdf-view-current-page)) + (pages (pdf-virtual-document-page-array + pdf-virtual-document)) + (page (aref pages (1- pn))) + (first-filepage (1+ (pdf-virtual-range-index-start page)))) + + (when (and (< n 0) + (not (= first-filepage pn))) + (cl-incf n)) + (setq pn first-filepage) + + (let (next) + (while (and (> n 0) + (setq next (pdf-virtual-document-next-file pn))) + (setq pn next) + (cl-decf n))) + (let (previous) + (while (and (< n 0) + (setq previous (pdf-virtual-document-previous-file pn))) + (setq pn previous) + (cl-incf n))) + (when interactive-p + (when (< n 0) + (message "First file.")) + (when (> n 0) + (message "Last file."))) + (pdf-view-goto-page pn) + n)) + +(defun pdf-virtual-buffer-backward-file (&optional n interactive-p) + (interactive "p\np") + (pdf-virtual-buffer-forward-file (- (or n 1)) interactive-p)) + + +;; * ================================================================== * +;; * Helper functions +;; * ================================================================== * + + +(defmacro pdf-virtual-dopages (bindings pages &rest body) + (declare (indent 2) (debug (sexp form &rest form))) + (let ((page (make-symbol "page"))) + `(dolist (,page ,pages) + (cl-destructuring-bind ,bindings + ,page + ,@body)))) + +(defun pdf-virtual--perform-search (string pages &optional regexp-p no-error) + (let* ((pages (pdf-virtual-document-normalize-pages pages)) + (file-pages (pdf-virtual-document-pages pages))) + (pdf-info-compose-queries + ((responses + (pdf-virtual-dopages (filename pages _region) + file-pages + (if regexp-p + (pdf-info-search-string string pages filename) + ;; FIXME: no-error won't work with synchronous calls. + (pdf-info-search-regexp string pages no-error filename))))) + (let (result) + (pdf-virtual-dopages (filename _ region) + file-pages + (let ((matches (pop responses))) + (when region + (setq matches + (mapcar + (lambda (m) + (let-alist m + `((edges . ,(pdf-util-edges-transform region .edges t)) + ,@m))) + (pdf-virtual--filter-edges + region matches + (apply-partially 'alist-get 'edges))))) + (dolist (m matches) + (push `((page . ,(pdf-virtual-document-page-of + filename (alist-get 'page m) + pages)) + ,@m) + result)))) + (nreverse result))))) + +(defun pdf-virtual--filter-edges (region elts &optional edges-key-fn) + (if (null region) + elts + (cl-remove-if-not + (lambda (edges) + (or (null edges) + (if (consp (car edges)) + (cl-some (apply-partially 'pdf-util-edges-intersection region) edges) + (pdf-util-edges-intersection region edges)))) + elts + :key edges-key-fn))) + +(defun pdf-virtual--transform-goto-dest (link filename region) + (let-alist link + (let ((local-page (pdf-virtual-document-page-of + filename .page))) + (if local-page + `((type . ,'goto-dest) + (title . , .title) + (page . ,local-page) + (top . ,(car (pdf-util-edges-transform + region (cons .top .top) t)))) + `((type . ,'goto-remote) + (title . , .title) + (filename . ,filename) + (page . , .page) + (top . , .top)))))) + + +;; * ================================================================== * +;; * Server adapter +;; * ================================================================== * + +(defmacro pdf-virtual-define-adapter (name arglist &optional doc &rest body) + ;; FIXME: Handle &optional + &rest argument. + (declare (doc-string 3) (indent 2) + (debug (&define name lambda-list + [&optional stringp] + def-body))) + (unless (stringp doc) + (push doc body) + (setq doc nil)) + (let ((fn (intern (format "pdf-virtual-%s" name))) + (base-fn (intern (format "pdf-info-%s" name))) + (base-fn-arg (make-symbol "fn")) + (true-file-or-buffer (make-symbol "true-file-or-buffer")) + (args (cl-remove-if (lambda (elt) + (memq elt '(&optional &rest))) + arglist))) + (unless (fboundp base-fn) + (error "Base function is undefined: %s" base-fn)) + (unless (memq 'file-or-buffer arglist) + (error "Argument list is missing a `file-or-buffer' argument: %s" arglist)) + `(progn + (put ',fn 'definition-name ',name) + (add-to-list 'pdf-virtual-adapter-alist ',(cons fn base-fn)) + (defun ,fn ,(cons base-fn-arg arglist) + ,(format "%sPDF virtual adapter to `%s'. + +This function delegates to `%s', unless the FILE-OR-BUFFER +argument denotes a VPDF document." + (if doc (concat doc "\n\n") "") + base-fn + base-fn) + (let ((,true-file-or-buffer + (cond + ((or (bufferp file-or-buffer) + (stringp file-or-buffer)) file-or-buffer) + ((or (null file-or-buffer) + ,(not (null (memq '&rest arglist)))) + (current-buffer))))) + (if (cond + ((null ,true-file-or-buffer) t) + ((bufferp ,true-file-or-buffer) + (not (pdf-virtual-buffer-p ,true-file-or-buffer))) + ((stringp ,true-file-or-buffer) + (not (pdf-virtual-filename-p ,true-file-or-buffer)))) + (,(if (memq '&rest arglist) 'apply 'funcall) ,base-fn-arg ,@args) + (when (stringp ,true-file-or-buffer) + (setq ,true-file-or-buffer + (find-file-noselect ,true-file-or-buffer))) + (save-current-buffer + (when (bufferp ,true-file-or-buffer) + (set-buffer ,true-file-or-buffer)) + ,@body))))))) + +(define-error 'pdf-virtual-unsupported-operation + "Operation not supported in VPDF buffer") + +(pdf-virtual-define-adapter open (&optional file-or-buffer password) + (mapc (lambda (file) + (pdf-info-open file password)) + (pdf-virtual-document-filenames))) + +(pdf-virtual-define-adapter close (&optional file-or-buffer) + (let ((files (cl-remove-if 'find-buffer-visiting + (pdf-virtual-document-filenames)))) + (pdf-info-compose-queries + ((results (mapc 'pdf-info-close files))) + (cl-some 'identity results)))) + +(pdf-virtual-define-adapter metadata (&optional file-or-buffer) + (pdf-info-compose-queries + ((md (mapc 'pdf-info-metadata (pdf-virtual-document-filenames)))) + (apply 'cl-mapcar (lambda (&rest elts) + (cons (caar elts) + (cl-mapcar 'cdr elts))) + md))) + +(pdf-virtual-define-adapter search-string (string &optional pages file-or-buffer) + (pdf-virtual--perform-search + string (pdf-virtual-document-normalize-pages pages))) + +(pdf-virtual-define-adapter search-regexp (pcre &optional + pages no-error file-or-buffer) + (pdf-virtual--perform-search + pcre (pdf-virtual-document-normalize-pages pages) 'regexp no-error)) + +(pdf-virtual-define-adapter pagelinks (page &optional file-or-buffer) + (cl-destructuring-bind (filename ext-page region) + (pdf-virtual-document-page page) + (pdf-info-compose-queries + ((links (pdf-info-pagelinks ext-page filename))) + (mapcar + (lambda (link) + (let-alist link + (if (not (eq .type 'goto-dest)) + link + `((edges . ,(pdf-util-edges-transform region .edges t)) + ,@(pdf-virtual--transform-goto-dest link filename region))))) + (pdf-virtual--filter-edges region (car links) 'car))))) + +(pdf-virtual-define-adapter number-of-pages (&optional file-or-buffer) + (pdf-info-compose-queries nil (pdf-virtual-document-number-of-pages))) + +(pdf-virtual-define-adapter outline (&optional file-or-buffer) + (let ((files (pdf-virtual-document-filenames))) + (pdf-info-compose-queries + ((outlines (mapc 'pdf-info-outline files))) + (cl-mapcan + (lambda (outline filename) + `(((depth . 1) + (type . goto-dest) + (title . ,filename) + (page . ,(pdf-virtual-document-page-of filename)) + (top . 0)) + ,@(delq + nil + (mapcar + (lambda (item) + (let-alist item + (if (not (eq .type 'goto-dest)) + `((depth . ,(1+ .depth)) + ,@item) + (cl-check-type filename string) + (let ((page (pdf-virtual-document-page-of + filename .page))) + (when page + `((depth . ,(1+ .depth)) + ,@(pdf-virtual--transform-goto-dest + item filename + (nth 2 (pdf-virtual-document-page page))))))))) + outline)))) + outlines files)))) + +(pdf-virtual-define-adapter gettext (page edges &optional + selection-style file-or-buffer) + (cl-destructuring-bind (filename file-page region) + (pdf-virtual-document-page page) + (let ((edges (pdf-util-edges-transform region edges))) + (pdf-info-gettext file-page edges selection-style filename)))) + +(pdf-virtual-define-adapter getselection (page edges &optional + selection-style file-or-buffer) + (cl-destructuring-bind (filename file-page region) + (pdf-virtual-document-page page) + (let ((edges (pdf-util-edges-transform region edges))) + (pdf-info-compose-queries + ((results (pdf-info-getselection file-page edges selection-style filename))) + (pdf-util-edges-transform + region + (pdf-virtual--filter-edges region (car results)) t))))) + +(pdf-virtual-define-adapter charlayout (page &optional edges-or-pos file-or-buffer) + (cl-destructuring-bind (filename file-page region) + (pdf-virtual-document-page page) + (let ((edges-or-pos (pdf-util-edges-transform region edges-or-pos))) + (pdf-info-compose-queries + ((results (pdf-info-charlayout file-page edges-or-pos filename))) + (mapcar (lambda (elt) + `(,(car elt) + . ,(pdf-util-edges-transform region (cdr elt) t))) + (pdf-virtual--filter-edges region (car results) 'cadr)))))) + +(pdf-virtual-define-adapter pagesize (page &optional file-or-buffer) + (cl-destructuring-bind (filename file-page region) + (pdf-virtual-document-page page) + (pdf-info-compose-queries + ((result (pdf-info-pagesize file-page filename))) + (if (null region) + (car result) + (pdf-util-with-edges (region) + (pdf-util-scale + (car result) (cons region-width region-height))))))) + +(pdf-virtual-define-adapter getannots (&optional pages file-or-buffer) + (let* ((pages (pdf-virtual-document-normalize-pages pages)) + (file-pages (pdf-virtual-document-pages pages))) + (pdf-info-compose-queries + ((annotations + (pdf-virtual-dopages (filename file-pages _region) + file-pages + (pdf-info-getannots file-pages filename)))) + (let ((page (car pages)) + result) + (pdf-virtual-dopages (_filename file-pages region) + file-pages + (dolist (a (pop annotations)) + (let ((edges (delq nil `(,(cdr (assq 'edges a)) + ,@(cdr (assq 'markup-edges a)))))) + (when (pdf-virtual--filter-edges region edges) + (let-alist a + (setcdr (assq 'page a) + (+ page (- .page (car file-pages)))) + (setcdr (assq 'id a) + (intern (format "%s/%d" .id (cdr (assq 'page a))))) + (when region + (when .edges + (setcdr (assq 'edges a) + (pdf-util-edges-transform region .edges t))) + (when .markup-edges + (setcdr (assq 'markup-edges a) + (pdf-util-edges-transform region .markup-edges t)))) + (push a result))))) + (cl-incf page (1+ (- (cdr file-pages) (car file-pages))))) + (nreverse result))))) + +(pdf-virtual-define-adapter getannot (id &optional file-or-buffer) + (let ((name (symbol-name id)) + page) + (save-match-data + (when (string-match "\\(.*\\)/\\([0-9]+\\)\\'" name) + (setq id (intern (match-string 1 name)) + page (string-to-number (match-string 2 name))))) + (if page + (cl-destructuring-bind (filename _ _) + (pdf-virtual-document-page page) + (pdf-info-compose-queries + ((result (pdf-info-getannot id filename))) + (let ((a (car result))) + (cl-destructuring-bind (_ _ region) + (pdf-virtual-document-page page) + (setcdr (assq 'page a) page) + (let-alist a + (setcdr (assq 'id a) + (intern (format "%s/%d" .id (cdr (assq 'page a))))) + (when region + (when .edges + (setcdr (assq 'edges a) + (pdf-util-edges-transform region .edges t))) + (when .markup-edges + (setcdr (assq 'markup-edges a) + (pdf-util-edges-transform region .markup-edges t)))))) + a))) + (pdf-info-compose-queries nil + (error "No such annotation: %s" id))))) + +(pdf-virtual-define-adapter addannot (page edges type &optional + file-or-buffer &rest markup-edges) + (signal 'pdf-virtual-unsupported-operation (list 'addannot))) + +(pdf-virtual-define-adapter delannot (id &optional file-or-buffer) + (signal 'pdf-virtual-unsupported-operation (list 'delannot))) + +(pdf-virtual-define-adapter mvannot (id edges &optional file-or-buffer) + (signal 'pdf-virtual-unsupported-operation (list 'mvannot))) + +(pdf-virtual-define-adapter editannot (id modifications &optional file-or-buffer) + (signal 'pdf-virtual-unsupported-operation (list 'editannot))) + +(pdf-virtual-define-adapter save (&optional file-or-buffer) + (signal 'pdf-virtual-unsupported-operation (list 'save))) + +;;(defvar-local pdf-virtual-annotation-mapping nil) + +(pdf-virtual-define-adapter getattachment-from-annot + (id &optional do-save file-or-buffer) + (let ((name (symbol-name id)) + page) + (save-match-data + (when (string-match "\\(.*\\)/\\([0-9]+\\)\\'" name) + (setq id (intern (match-string 1 name)) + page (string-to-number (match-string 2 name))))) + (if page + (cl-destructuring-bind (filename _ _) + (pdf-virtual-document-page page) + (pdf-info-getattachment-from-annot id do-save filename)) + (pdf-info-compose-queries nil + (error "No such annotation: %s" id))))) + +(pdf-virtual-define-adapter getattachments (&optional do-save file-or-buffer) + (pdf-info-compose-queries + ((results (mapc + (lambda (f) + (pdf-info-getattachments do-save f)) + (pdf-virtual-document-filenames)))) + (apply 'append results))) + +(pdf-virtual-define-adapter synctex-forward-search + (source &optional line column file-or-buffer) + (signal 'pdf-virtual-unsupported-operation (list 'synctex-forward-search))) + +(pdf-virtual-define-adapter synctex-backward-search (page &optional x y file-or-buffer) + (cl-destructuring-bind (filename file-page region) + (pdf-virtual-document-page page) + (cl-destructuring-bind (x &rest y) + (pdf-util-edges-transform region (cons x y)) + (pdf-info-synctex-backward-search file-page x y filename)))) + +(pdf-virtual-define-adapter renderpage (page width &optional file-or-buffer + &rest commands) + (when (keywordp file-or-buffer) + (push file-or-buffer commands) + (setq file-or-buffer nil)) + (cl-destructuring-bind (filename file-page region) + (pdf-virtual-document-page page) + (when region + (setq commands (append (list :crop-to region) commands) + width (pdf-util-with-edges (region) + (round (* width (max 1 (/ 1.0 (max 1e-6 region-width)))))))) + (apply 'pdf-info-renderpage file-page width filename commands))) + +(pdf-virtual-define-adapter boundingbox (page &optional file-or-buffer) + (cl-destructuring-bind (filename file-page region) + (pdf-virtual-document-page page) + (pdf-info-compose-queries + ((results (unless region (pdf-info-boundingbox file-page filename)))) + (if region + (list 0 0 1 1) + (car results))))) + +(pdf-virtual-define-adapter pagelabels (&optional file-or-buffer) + (signal 'pdf-virtual-unsupported-operation (list 'pagelabels))) + +(pdf-virtual-define-adapter setoptions (&optional file-or-buffer &rest options) + (when (keywordp file-or-buffer) + (push file-or-buffer options) + (setq file-or-buffer nil)) + (pdf-info-compose-queries + ((_ (dolist (f (pdf-virtual-document-filenames)) + (apply 'pdf-info-setoptions f options)))) + nil)) + +(pdf-virtual-define-adapter getoptions (&optional file-or-buffer) + (signal 'pdf-virtual-unsupported-operation (list 'getoptions))) + +(pdf-virtual-define-adapter encrypted-p (&optional file-or-buffer) + nil) + +(provide 'pdf-virtual) +;;; pdf-virtual.el ends here diff --git a/elpa/pdf-tools-20211110.513/pdf-virtual.elc b/elpa/pdf-tools-20211110.513/pdf-virtual.elc Binary files differ. diff --git a/elpa/tablist-20200427.2205/tablist-autoloads.el b/elpa/tablist-20200427.2205/tablist-autoloads.el @@ -0,0 +1,51 @@ +;;; tablist-autoloads.el --- automatically extracted autoloads +;; +;;; Code: + +(add-to-list 'load-path (directory-file-name + (or (file-name-directory #$) (car load-path)))) + + +;;;### (autoloads nil "tablist" "tablist.el" (0 0 0 0)) +;;; Generated autoloads from tablist.el + +(autoload 'tablist-minor-mode "tablist" "\ +Toggle Tablist minor mode on or off. + +If called interactively, enable Tablist minor mode if ARG is +positive, and disable it if ARG is zero or negative. If called +from Lisp, also enable the mode if ARG is omitted or nil, and +toggle it if ARG is `toggle'; disable the mode otherwise. + +\\{tablist-minor-mode-map} + +\(fn &optional ARG)" t nil) + +(autoload 'tablist-mode "tablist" "\ + + +\(fn)" t nil) + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "tablist" '("tablist-"))) + +;;;*** + +;;;### (autoloads nil "tablist-filter" "tablist-filter.el" (0 0 0 +;;;;;; 0)) +;;; Generated autoloads from tablist-filter.el + +(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "tablist-filter" '("tablist-filter-"))) + +;;;*** + +;;;### (autoloads nil nil ("tablist-pkg.el") (0 0 0 0)) + +;;;*** + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; coding: utf-8 +;; End: +;;; tablist-autoloads.el ends here diff --git a/elpa/tablist-20200427.2205/tablist-filter.el b/elpa/tablist-20200427.2205/tablist-filter.el @@ -0,0 +1,464 @@ +;;; tablist-filter.el --- Filter expressions for tablists. -*- lexical-binding:t -*- + +;; Copyright (C) 2013, 2014 Andreas Politz + +;; Author: Andreas Politz <politza@fh-trier.de> +;; Keywords: extensions, lisp + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; + +(defvar python-mode-hook) +(let (python-mode-hook) ;FIXME: Why? +(require 'semantic/wisent/comp) +(require 'semantic/wisent/wisent)) + +;;; Code: + +(defvar wisent-eoi-term) +(declare-function wisent-parse "semantic/wisent/wisent.el") + +;; +;; *Variables +;; + +(defvar tablist-filter-binary-operator + '((== . tablist-filter-op-equal) + (=~ . tablist-filter-op-regexp) + (< . tablist-filter-op-<) + (> . tablist-filter-op->) + (<= . tablist-filter-op-<=) + (>= . tablist-filter-op->=) + (= . tablist-filter-op-=))) + +(defvar tablist-filter-unary-operator nil) + +(defvar tablist-filter-wisent-parser nil) + +(defvar tablist-filter-lexer-regexps nil) + +(defvar tablist-filter-wisent-grammar + '( + ;; terminals + ;; Use lowercase for better looking error messages. + (operand unary-operator binary-operator or and not) + + ;; terminal associativity & precedence + ((left binary-operator) + (left unary-operator) + (left or) + (left and) + (left not)) + + ;; rules + (filter-or-empty + ((nil)) + ((?\( ?\)) nil) + ((filter) $1)) + (filter + ((operand) $1) ;;Named filter + ((operand binary-operator operand) `(,(intern $2) ,$1 ,$3)) + ((unary-operator operand) `(,(intern $1) ,$2)) + ((not filter) `(not ,$2)) + ((filter and filter) `(and ,$1 ,$3)) + ((filter or filter) `(or ,$1 ,$3)) + ((?\( filter ?\)) $2)))) + +;; +;; *Filter Parsing +;; + +(defun tablist-filter-parser-init (&optional reinitialize interactive) + (interactive (list t t)) + (unless (and tablist-filter-lexer-regexps + (not reinitialize)) + (let ((re (mapcar + (lambda (l) + (let ((re (regexp-opt + (mapcar 'symbol-name + (mapcar 'car l)) t))) + (if (= (length re) 0) + ".\\`" ;;matches nothing + re))) + (list tablist-filter-binary-operator + tablist-filter-unary-operator)))) + (setq tablist-filter-lexer-regexps + (nreverse + (cons (concat "\\(?:" (car re) "\\|" (cadr re) + "\\|[ \t\f\r\n\"!()]\\|&&\\|||\\)") + re))))) + (unless (and tablist-filter-wisent-parser + (not reinitialize)) + (let ((wisent-compile-grammar* + (symbol-function + 'wisent-compile-grammar))) + (setq tablist-filter-wisent-parser + ;; Trick the byte-compile into not using the byte-compile + ;; handler in semantic/wisent/comp.el, since it does not + ;; always work (wisent-context-compile-grammar n/a). + (funcall wisent-compile-grammar* + tablist-filter-wisent-grammar)))) + (when interactive + (message "Parser reinitialized.")) + nil) + +(defun tablist-filter-wisent-lexer () + (cl-destructuring-bind (unary-op binary-op keywords) + tablist-filter-lexer-regexps + (skip-chars-forward " \t\f\r\n") + (cond + ((eobp) (list wisent-eoi-term)) + ((= ?\" (char-after)) + `(operand , (condition-case err + (read (current-buffer)) + (error (signal (car err) (cons + "invalid lisp string" + (cdr err))))))) + ((looking-at unary-op) + (goto-char (match-end 0)) + `(unary-operator ,(match-string-no-properties 0))) + ((looking-at binary-op) + (goto-char (match-end 0)) + `(binary-operator ,(match-string-no-properties 0))) + ((looking-at "&&") + (forward-char 2) + `(and "&&")) + ((looking-at "||") + (forward-char 2) + `(or "||")) + ((= ?! (char-after)) + (forward-char) + `(not "!")) + ((= ?\( (char-after)) + (forward-char) + `(?\( "(")) + ((= ?\) (char-after)) + (forward-char) + `(?\) ")")) + (t + (let ((beg (point))) + (when (re-search-forward keywords nil 'move) + (goto-char (match-beginning 0))) + `(operand ,(buffer-substring-no-properties + beg + (point)))))))) + +(defun tablist-filter-parse (filter) + (interactive "sFilter: ") + (tablist-filter-parser-init) + (with-temp-buffer + (save-excursion (insert filter)) + (condition-case error + (wisent-parse tablist-filter-wisent-parser + 'tablist-filter-wisent-lexer + (lambda (msg) + (signal 'error + (replace-regexp-in-string + "\\$EOI" "end of input" + msg t t)))) + (error + (signal 'error + (append (if (consp (cdr error)) + (cdr error) + (list (cdr error))) + (list (point)))))))) + +(defun tablist-filter-unparse (filter &optional noerror) + (cl-labels + ((unparse (filter &optional noerror) + (cond + ((stringp filter) + (if (or (string-match (nth 2 tablist-filter-lexer-regexps) + filter) + (= 0 (length filter))) + (format "%S" filter) + filter)) + ((and (eq (car-safe filter) 'not) + (= (length filter) 2)) + (let ((paren (memq (car-safe (nth 1 filter)) '(or and)))) + (format "!%s%s%s" + (if paren "(" "") + (unparse (cadr filter) noerror) + (if paren ")" "")))) + ((and (memq (car-safe filter) '(and or)) + (= (length filter) 3)) + (let ((lparen (and (eq (car filter) 'and) + (eq 'or (car-safe (car-safe (cdr filter)))))) + (rparen (and (eq (car filter) 'and) + (eq 'or (car-safe (car-safe (cddr filter))))))) + (format "%s%s%s %s %s%s%s" + (if lparen "(" "") + (unparse (cadr filter) noerror) + (if lparen ")" "") + (cl-case (car filter) + (and "&&") (or "||")) + (if rparen "(" "") + (unparse (car (cddr filter)) noerror) + (if rparen ")" "")))) + ((and (assq (car-safe filter) tablist-filter-binary-operator) + (= (length filter) 3)) + (format "%s %s %s" + (unparse (cadr filter) noerror) + (car filter) + (unparse (car (cddr filter)) noerror))) + ((and (assq (car-safe filter) tablist-filter-unary-operator) + (= (length filter) 2)) + (format "%s %s" + (car filter) + (unparse (cadr filter) noerror))) + ((not filter) "") + (t (funcall (if noerror 'format 'error) + "Invalid filter: %s" filter))))) + (tablist-filter-parser-init) + (unparse filter noerror))) + +(defun tablist-filter-eval (filter id entry &optional named-alist) + (cl-labels + ((feval (filter) + (pcase filter + (`(not . ,(and operand (guard (not (cdr operand))))) + (not (feval (car operand)))) + (`(and . ,(and operands (guard (= 2 (length operands))))) + (and + (feval (nth 0 operands)) + (feval (nth 1 operands)))) + (`(or . ,(and operands (guard (= 2 (length operands))))) + (or + (feval (nth 0 operands)) + (feval (nth 1 operands)))) + (`(,op . ,(and operands (guard (= (length operands) 1)))) + (let ((fn (assq op tablist-filter-unary-operator))) + (unless fn + (error "Undefined unary operator: %s" op)) + (funcall fn id entry (car operands)))) + (`(,op . ,(and operands (guard (= (length operands) 2)))) + (let ((fn (cdr (assq op tablist-filter-binary-operator)))) + (unless fn + (error "Undefined binary operator: %s" op)) + (funcall fn id entry (car operands) + (cadr operands)))) + ((guard (stringp filter)) + (let ((fn (cdr (assoc filter named-alist)))) + (unless fn + (error "Undefined named filter: %s" filter)) + (if (functionp fn) + (funcall fn id entry)) + (feval + (if (stringp fn) (tablist-filter-unparse fn) fn)))) + (`nil t) + (_ (error "Invalid filter: %s" filter))))) + (feval filter))) + +;; +;; *Filter Operators +;; + +(defun tablist-filter-get-item-by-name (entry col-name) + (let* ((col (cl-position col-name tabulated-list-format + :key 'car + :test + (lambda (s1 s2) + (eq t (compare-strings + s1 nil nil s2 nil nil t))))) + (item (and col (elt entry col)))) + (unless col + (error "No such column: %s" col-name)) + (if (consp item) ;(LABEL . PROPS) + (car item) + item))) + +(defun tablist-filter-op-equal (_id entry op1 op2) + "COLUMN == STRING : Matches if COLUMN's entry is equal to STRING." + (let ((item (tablist-filter-get-item-by-name entry op1))) + (string= item op2))) + +(defun tablist-filter-op-regexp (_id entry op1 op2) + "COLUMN =~ REGEXP : Matches if COLUMN's entry matches REGEXP." + (let ((item (tablist-filter-get-item-by-name entry op1))) + (string-match op2 item))) + +(defun tablist-filter-op-< (id entry op1 op2) + "COLUMN < NUMBER : Matches if COLUMN's entry is less than NUMBER." + (tablist-filter-op-numeric '< id entry op1 op2)) + +(defun tablist-filter-op-> (id entry op1 op2) + "COLUMN > NUMBER : Matches if COLUMN's entry is greater than NUMBER." + (tablist-filter-op-numeric '> id entry op1 op2)) + +(defun tablist-filter-op-<= (id entry op1 op2) + "COLUMN <= NUMBER : Matches if COLUMN's entry is less than or equal to NUMBER." + (tablist-filter-op-numeric '<= id entry op1 op2)) + +(defun tablist-filter-op->= (id entry op1 op2) + "COLUMN >= NUMBER : Matches if COLUMN's entry is greater than or equal to NUMBER." + (tablist-filter-op-numeric '>= id entry op1 op2)) + +(defun tablist-filter-op-= (id entry op1 op2) + "COLUMN = NUMBER : Matches if COLUMN's entry as a number is equal to NUMBER." + (tablist-filter-op-numeric '= id entry op1 op2)) + +(defun tablist-filter-op-numeric (op _id entry op1 op2) + (let ((item (tablist-filter-get-item-by-name entry op1))) + (funcall op (string-to-number item) + (string-to-number op2)))) + +(defun tablist-filter-help (&optional temporary) + (interactive) + (cl-labels + ((princ-op (op) + (princ (car op)) + (princ (concat (make-string (max 0 (- 4 (length (symbol-name (car op))))) + ?\s) + "- " + (car (split-string + (or (documentation (cdr op)) + (format "FIXME: Not documented: %s" + (cdr op))) + "\n" t)) + "\n")))) + (with-temp-buffer-window + "*Help*" + (if temporary + '((lambda (buf alist) + (let ((win + (or (display-buffer-reuse-window buf alist) + (display-buffer-in-side-window buf alist)))) + (fit-window-to-buffer win) + win)) + (side . bottom))) + nil + (princ "Filter entries with the following operators.\n\n") + (princ "&& - FILTER1 && FILTER2 : Locical and.\n") + (princ "|| - FILTER1 || FILTER2 : Locical or.\n") + (dolist (op tablist-filter-binary-operator) + (princ-op op)) + (princ "! - ! FILTER : Locical not.\n\n") + (dolist (op tablist-filter-unary-operator) + (princ-op op)) + (princ "\"...\" may be used to quote names and values if necessary, +and \(...\) to group expressions.") + (with-current-buffer standard-output + (help-mode))))) + +;; +;; *Filter Functions +;; + +;; filter ::= nil | named | fn | (OP OP1 [OP2]) + +(defun tablist-filter-negate (filter) + "Return a filter not matching filter." + (cond + ((eq (car-safe filter) 'not) + (cadr filter)) + (filter + (list 'not filter)))) + +(defun tablist-filter-push (filter new-filter &optional or-p) + "Return a filter combining FILTER and NEW-FILTER. + +By default the filters are and'ed, unless OR-P is non-nil." + (if (or (null filter) + (null new-filter)) + (or filter + new-filter) + (list (if or-p 'or 'and) + filter new-filter))) + +(defun tablist-filter-pop (filter) + "Remove the first operator or operand from filter. + +If filter starts with a negation, return filter unnegated, +if filter starts with a dis- or conjunction, remove the first operand, +if filter is nil, raise an error, +else return nil." + (pcase filter + (`(,(or `and `or) . ,tail) + (car (cdr tail))) + (`(not . ,op1) + (car op1)) + (_ (unless filter + (error "Filter is empty"))))) + +(defun tablist-filter-map (fn filter) + (pcase filter + (`(,(or `and `or `not) . ,tail) + (cons (car filter) + (mapcar (lambda (f) + (tablist-filter-map fn f)) + tail))) + (_ (funcall fn filter)))) + +;; +;; *Reading Filter +;; + +(defvar tablist-filter-edit-history nil) +(defvar tablist-filter-edit-display-help t) + +(defun tablist-filter-edit-filter (prompt &optional + initial-filter history + validate-fn) + (let* ((str (tablist-filter-unparse initial-filter)) + (filter initial-filter) + (validate-fn (or validate-fn 'identity)) + error done) + (save-window-excursion + (when tablist-filter-edit-display-help + (tablist-filter-help t)) + (while (not done) + (minibuffer-with-setup-hook + (lambda () + (when error + (when (car error) + (goto-char (+ (field-beginning) + (car error))) + (skip-chars-backward " \t\n")) + (minibuffer-message "%s" (cdr error)) + (setq error nil))) + (setq str (propertize + (read-string prompt str + (or history 'tablist-filter-edit-history))) + done t)) + (condition-case err + (progn + (setq filter (tablist-filter-parse str)) + (funcall validate-fn filter)) + (error + (setq done nil) + (setq error (cons (car-safe (cddr err)) nil)) + (when (car error) + (setq str (with-temp-buffer + (insert str) + (goto-char (car error)) + (set-text-properties + (progn + (skip-chars-backward " \t\n") + (backward-char) + (point)) + (min (car error) (point-max)) + '(face error rear-nonsticky t)) + (buffer-string)))) + (setcdr error (error-message-string err))))) + filter))) + +(provide 'tablist-filter) +;; Local Variables: +;; outline-regexp: ";;\\(\\(?:[;*]+ \\| \\*+\\)[^\s\t\n]\\|###autoload\\)\\|(" +;; indent-tabs-mode: nil +;; End: +;;; tablist-filter.el ends here diff --git a/elpa/tablist-20200427.2205/tablist-filter.elc b/elpa/tablist-20200427.2205/tablist-filter.elc Binary files differ. diff --git a/elpa/tablist-20200427.2205/tablist-pkg.el b/elpa/tablist-20200427.2205/tablist-pkg.el @@ -0,0 +1,11 @@ +(define-package "tablist" "20200427.2205" "Extended tabulated-list-mode" + '((emacs "24.3")) + :commit "faab7a035ef2258cc4ea2182f67e3aedab7e2af9" :authors + '(("Andreas Politz" . "politza@fh-trier.de")) + :maintainer + '("Andreas Politz" . "politza@fh-trier.de") + :keywords + '("extensions" "lisp")) +;; Local Variables: +;; no-byte-compile: t +;; End: diff --git a/elpa/tablist-20200427.2205/tablist.el b/elpa/tablist-20200427.2205/tablist.el @@ -0,0 +1,1945 @@ +;;; tablist.el --- Extended tabulated-list-mode -*- lexical-binding: t -*- + +;; Copyright (C) 2013, 2014 Andreas Politz + +;; Author: Andreas Politz <politza@fh-trier.de> +;; Keywords: extensions, lisp +;; Package: tablist +;; Version: 1.0 +;; Package-Requires: ((emacs "24.3")) + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: +;; +;; This package adds marks and filters to tabulated-list-mode. It +;; also kind of puts a dired face on tabulated list buffers. +;; +;; It can be used by deriving from tablist-mode and some features by +;; using tablist-minor-mode inside a tabulated-list-mode buffer. +;; + +;;; Code: + +(require 'cl-lib) +(require 'ring) +(require 'tabulated-list) +(require 'dired) +(require 'tablist-filter) + +;; +;; *Macros +;; + +(defmacro tablist-save-marks (&rest body) + "Eval body, while preserving all marks." + (let ((marks (make-symbol "marks"))) + `(let (,marks) + (save-excursion + (goto-char (point-min)) + (let ((re "^\\([^ ]\\)")) + (while (re-search-forward re nil t) + (push (cons (tabulated-list-get-id) + (tablist-get-mark-state)) + ,marks)))) + (unwind-protect + (progn ,@body) + (save-excursion + (dolist (m ,marks) + (let ((id (pop m))) + (goto-char (point-min)) + (while (and id (not (eobp))) + (when (equal id (tabulated-list-get-id)) + (tablist-put-mark-state m) + (setq id nil)) + (forward-line))))))))) + +(defmacro tablist-with-remembering-entry (&rest body) + "Remember where body left of and restore previous position. + +If the current entry is still visible, move to it. Otherwise move +to the next visible one after it. If that also fails, goto to +the beginning of the buffer. Finally move point to the major +column." + (declare (indent 0) (debug t)) + (let ((re (make-symbol "re")) + (id (make-symbol "id")) + (col (make-symbol "col"))) + `(let ((,re + (replace-regexp-in-string + "[\t ]+" "[\t ]*" (regexp-quote + (or (thing-at-point 'line) "")) + t t)) + (,id (tabulated-list-get-id)) + (,col (tablist-current-column))) + (progn + ,@body + (let (success pos) + (goto-char (point-min)) + (setq pos (point)) + (while (and (setq success (re-search-forward ,re nil t)) + (> (point) (prog1 pos (setq pos (point)))) + (forward-line -1) + (not (equal ,id (tabulated-list-get-id)))) + (forward-line)) + (unless success + (goto-char (point-min)) + (while (and (not (eobp)) + (not success)) + (if (equal (tabulated-list-get-id) ,id) + (setq success t) + (forward-line)))) + (unless (and success (not (invisible-p (point)))) + (goto-char (point-min))) + (tablist-skip-invisible-entries) + (tablist-move-to-column + (or ,col (car (tablist-major-columns)))) + (dolist (win (get-buffer-window-list)) + (set-window-point win (point)))))))) + +(defmacro tablist-with-filter-displayed (&rest body) + "Display the current filter in the mode while evalling BODY." + (let ((state (make-symbol "state"))) + `(let ((,state (tablist-display-filter 'state))) + (tablist-display-filter t) + (unwind-protect + (progn ,@body) + (tablist-display-filter ,state))))) + +;; +;; *Mode Maps +;; + +(defvar tablist-mode-filter-map + (let ((kmap (make-sparse-keymap))) + (define-key kmap "p" #'tablist-pop-filter) + (define-key kmap "r" #'tablist-push-regexp-filter) + (define-key kmap "=" #'tablist-push-equal-filter) + (define-key kmap "n" #'tablist-push-numeric-filter) + (define-key kmap "!" #'tablist-negate-filter) + (define-key kmap "t" #'tablist-toggle-first-filter-logic) + (define-key kmap "/" #'tablist-display-filter) + (define-key kmap "z" #'tablist-suspend-filter) + + (define-key kmap "a" #'tablist-push-named-filter) + (define-key kmap "s" #'tablist-name-current-filter) + (define-key kmap "D" #'tablist-delete-named-filter) + (define-key kmap "d" #'tablist-deconstruct-named-filter) + (define-key kmap "e" #'tablist-edit-filter) + (define-key kmap "C" #'tablist-clear-filter) + kmap)) + +(defvar tablist-mode-mark-map + (let ((kmap (make-sparse-keymap))) + (define-key kmap "c" #'tablist-change-marks) + (define-key kmap "!" #'tablist-unmark-all-marks) + (define-key kmap "r" #'tablist-mark-items-regexp) + (define-key kmap "n" #'tablist-mark-items-numeric) + (define-key kmap "m" #'tablist-mark-forward) + kmap)) + +(defvar tablist-mode-regexp-map + (let ((kmap (make-sparse-keymap))) + ;; (define-key kmap "&" #'tablist-flag-gargabe-items) + (define-key kmap "m" #'tablist-mark-items-regexp) + kmap)) + +(defvar tablist-minor-mode-map + (let ((kmap (make-sparse-keymap))) + (define-key kmap "m" #'tablist-mark-forward) + (define-key kmap (kbd "DEL") #'tablist-unmark-backward) + (define-key kmap "k" #'tablist-do-kill-lines) + (define-key kmap "U" #'tablist-unmark-all-marks) + (define-key kmap "u" #'tablist-unmark-forward) + (define-key kmap "t" #'tablist-toggle-marks) + + (define-key kmap (kbd "TAB") #'tablist-forward-column) + (define-key kmap "\t" #'tablist-forward-column) + (define-key kmap [backtab] #'tablist-backward-column) + + (define-key kmap "%" tablist-mode-regexp-map) + (define-key kmap "*" tablist-mode-mark-map) + (define-key kmap "/" tablist-mode-filter-map) + + ;; (define-key kmap "e" #'tablist-edit-column) + ;; (define-key kmap "i" #'tablist-insert-entry) + (define-key kmap "s" #'tablist-sort) + (define-key kmap [remap back-to-indentation] #'tablist-move-to-major-column) + (define-key kmap [remap next-line] #'tablist-next-line) + (define-key kmap [remap previous-line] #'tablist-previous-line) + (define-key kmap "<" #'tablist-shrink-column) + (define-key kmap ">" #'tablist-enlarge-column) + (define-key kmap "q" #'tablist-quit) + (define-key kmap "G" #'tablist-revert) + (define-key kmap (kbd "C-c C-e") #'tablist-export-csv) + kmap)) + +(defvar tablist-mode-map + (let ((kmap (copy-keymap tablist-minor-mode-map))) + (set-keymap-parent kmap tabulated-list-mode-map) + (define-key kmap "d" #'tablist-flag-forward) + (define-key kmap (kbd "RET") #'tablist-find-entry) + (define-key kmap "f" #'tablist-find-entry) + ;; (define-key kmap "~" #'tablist-flag-gargabe-items) + (define-key kmap "D" #'tablist-do-delete) + ;; (define-key kmap "C" #'tablist-do-copy) + ;; (define-key kmap "R" #'tablist-do-rename) + (define-key kmap "x" #'tablist-do-flagged-delete) + ;; (define-key kmap "F" #'tablist-find-marked-items) + ;; (define-key kmap (kbd "C-o") #'tablist-display-item) + kmap)) + +;; +;; *Variables +;; + +;; Marking +(defvar tablist-umark-filtered-entries t) +(defvar tablist-marker-char dired-marker-char + "The character used for marking.") +(defvar tablist-marker-face 'dired-mark + "The face used for the mark character.") +(defvar tablist-marked-face 'dired-marked + "The face used for marked major columns.") + +;; Operations +(defvar-local tablist-operations-function nil + "A function for handling operations on the entries. + +The function is called with varying number of arguments, while +the first one is always a symbol describing one of the following +operations. + +`supported-operations' + +This is the only mandatory operation. There are no other +arguments and the function should return a list of symbols of +supported operations. + +`delete' + +The 2nd argument will be a list of entry ID's. The function +should somehow delete these entries and update +`tabulated-list-entries'. + +`find-entry' + +The 2nd argument is the ID of an entry. The function should +somehow find/display this entry, i.e. a kind of default +operation. + +`edit-column' + +The function is called with 3 further arguments: ID, INDEX and +NEW-COLUMN, where ID represents the entry to edit, INDEX is the index +of the column and NEW-COLUMN is the proposed new value for this +column. It should either + +i. return a new edited complete entry and update +`tabulated-list-entries', or + +ii. throw an error, if NEW-COLUMN is not a valid value for this +column. + +`complete' + +The function is called with 4 further arguments: ID, INDEX, +STRING and POS, where ID represents an entry, INDEX is the index +of the column to complete, STRING it's current value and POS an +offset of the current position of point into STRING. + +The function should return a collection for this column, suitable +as argument for the function `completion-in-region'.") + +;; Differentiating columns +(defvar-local tablist-major-columns nil + "Columns used to mark and when querying.") + +;; Filter +(defvar-local tablist-current-filter nil) +(defvar-local tablist-filter-suspended nil) +(defvar tablist-named-filter nil) + +;; History variables +(defvar tablist-column-name-history nil) + +;; Hooks +(defvar tablist-selection-changed-functions nil + "A hook run when ever point moves to a different entry.") + +;; Context Window +(defvar-local tablist-context-window nil) +(defvar-local tablist-context-window-function nil) +(defvar tablist-context-window-display-action + `((display-buffer-reuse-window + tablist-display-buffer-split-below-and-attach) + (window-height . 0.25) + (inhibit-same-window . t))) + +;; +;; *Setup +;; + +(defvar savehist-additional-variables) +(add-hook 'savehist-save-hook + (lambda nil + (add-to-list 'savehist-additional-variables 'tablist-named-filter))) + +;;;###autoload +(define-minor-mode tablist-minor-mode + nil nil nil nil + (unless (derived-mode-p 'tabulated-list-mode) + (error "Buffer is not in Tabulated List Mode")) + (tablist-init (not tablist-minor-mode))) + +;;;###autoload +(define-derived-mode tablist-mode tabulated-list-mode "TL" + (tablist-init)) + +(defun tablist-init (&optional disable) + (let ((cleaned-misc (cl-remove 'tablist-current-filter + mode-line-misc-info :key #'car-safe))) + (cond + ((not disable) + (set (make-local-variable 'mode-line-misc-info) + (append + (list + (list 'tablist-current-filter + '(:eval (format " [%s]" + (if tablist-filter-suspended + "suspended" + "filtered"))))))) + (add-hook 'post-command-hook + 'tablist-selection-changed-handler nil t) + (add-hook 'tablist-selection-changed-functions + 'tablist-context-window-update nil t)) + (t + (setq mode-line-misc-info cleaned-misc) + (remove-hook 'post-command-hook + 'tablist-selection-changed-handler t) + (remove-hook 'tablist-selection-changed-functions + 'tablist-context-window-update t))))) + +(defun tablist-quit () + (interactive) + (tablist-hide-context-window) + (quit-window)) + +(defvar-local tablist-selected-id nil) +(defvar tablist-edit-column-minor-mode) + +(defun tablist-selection-changed-handler () + (unless tablist-edit-column-minor-mode + (let ((id tablist-selected-id) + (selected (tabulated-list-get-id))) + (unless (eq selected id) + (setq tablist-selected-id selected) + (run-hook-with-args + 'tablist-selection-changed-functions + tablist-selected-id))))) + +(defvar tablist-context-window-update--timer nil) + +(defun tablist-context-window-update (&optional id) + (when (and tablist-context-window-function + (window-live-p tablist-context-window) + (not tablist-edit-column-minor-mode)) + (unless id + (setq id (tabulated-list-get-id))) + (when (timerp tablist-context-window-update--timer) + (cancel-timer tablist-context-window-update--timer)) + (setq tablist-context-window-update--timer + (run-with-idle-timer 0.1 nil + (lambda (fn window) + (when (window-live-p window) + (with-selected-window window + (set-window-dedicated-p nil nil) + (save-selected-window + (funcall fn id)) + (when (window-live-p (selected-window)) + (set-window-dedicated-p nil t))))) + tablist-context-window-function + tablist-context-window)))) + +(defun tablist-display-context-window () + (interactive) + (unless tablist-context-window-function + (error "No function for handling a context is defined")) + (unless (window-live-p tablist-context-window) + (setq tablist-context-window + (display-buffer + (current-buffer) + tablist-context-window-display-action))) + (prog1 + tablist-context-window + (tablist-context-window-update))) + +(defun tablist-hide-context-window () + (interactive) + (when (window-live-p tablist-context-window) + (let ((ignore-window-parameters t)) + (delete-window tablist-context-window))) + (setq tablist-context-window nil)) + +(defun tablist-toggle-context-window () + (interactive) + (if (window-live-p tablist-context-window) + (tablist-hide-context-window) + (tablist-display-context-window))) + +;; +;; *Marking +;; + +(defun tablist-revert () + "Revert the list with marks preserved, position kept." + (interactive) + (tablist-save-marks + (tablist-with-remembering-entry + (tabulated-list-revert)))) + +(defun tablist-major-columns () + (if (null tablist-major-columns) + (number-sequence 0 (1- (length tabulated-list-format))) + (if (numberp tablist-major-columns) + (list tablist-major-columns) + tablist-major-columns))) + +(defun tablist-put-mark (&optional pos) + "Put a mark before the entry at POS. + +POS defaults to point. Use `tablist-marker-char', +`tablist-marker-face', `tablist-marked-face' and +`tablist-major-columns' to determine how to mark and what to put +a face on." + (when (or (null tabulated-list-padding) + (< tabulated-list-padding 1)) + (setq tabulated-list-padding 1) + (tabulated-list-revert)) + (save-excursion + (and pos (goto-char pos)) + (unless (tabulated-list-get-id) + (error "No entry at this position")) + (let ((inhibit-read-only t)) + (tabulated-list-put-tag + (string tablist-marker-char)) + (put-text-property + (point-at-bol) + (1+ (point-at-bol)) + 'face tablist-marker-face) + (let ((columns (tablist-column-offsets))) + (dolist (c (tablist-major-columns)) + (when (and (>= c 0) + (< c (length columns))) + (let ((beg (+ (point-at-bol) + (nth c columns))) + (end (if (= c (1- (length columns))) + (point-at-eol) + (+ (point-at-bol) + (nth (1+ c) columns))))) + (cond + ((and tablist-marked-face + (not (eq tablist-marker-char ?\s))) + (tablist--save-face-property beg end) + (put-text-property + beg end 'face tablist-marked-face)) + (t (tablist--restore-face-property beg end)))))))))) + +(defun tablist-mark-forward (&optional arg interactive) + "Mark ARG entries forward. + +ARG is interpreted as a prefix-arg. If interactive is non-nil, +maybe use the active region instead of ARG. + +See `tablist-put-mark' for how entries are marked." + (interactive (list current-prefix-arg t)) + (cond + ;; Mark files in the active region. + ((and interactive (use-region-p)) + (save-excursion + (goto-char (region-beginning)) + (beginning-of-line) + (tablist-repeat-over-lines + (1+ (count-lines + (point) + (save-excursion + (goto-char (region-end)) + (beginning-of-line) + (point)))) + 'tablist-put-mark))) + ;; Mark the current (or next ARG) files. + (t + (tablist-repeat-over-lines + (prefix-numeric-value arg) + 'tablist-put-mark)))) + +(defun tablist-mark-backward (&optional arg interactive) + "Mark ARG entries backward. + +See `tablist-mark-forward'." + (interactive (list current-prefix-arg t)) + (tablist-mark-forward (- (prefix-numeric-value arg)) + interactive)) + +(defun tablist-unmark-forward (&optional arg interactive) + "Unmark ARG entries forward. + +See `tablist-mark-forward'." + (interactive (list current-prefix-arg t)) + (let ((tablist-marker-char ?\s) + tablist-marked-face) + (tablist-mark-forward arg interactive))) + +(defun tablist-unmark-backward (&optional arg interactive) + "Unmark ARG entries backward. + +See `tablist-mark-forward'." + (interactive (list current-prefix-arg t)) + (let ((tablist-marker-char ?\s) + tablist-marked-face) + (tablist-mark-backward arg interactive))) + +(defun tablist-flag-forward (&optional arg interactive) + "Flag ARG entries forward. + +See `tablist-mark-forward'." + (interactive (list current-prefix-arg t)) + (let ((tablist-marker-char ?D) + (tablist-marked-face 'dired-flagged)) + (tablist-mark-forward arg interactive))) + +(defun tablist-change-marks (old new) + "Change all OLD marks to NEW marks. + +OLD and NEW are both characters used to mark files." + (interactive + (let* ((cursor-in-echo-area t) + (old (progn (message "Change (old mark): ") (read-char))) + (new (progn (message "Change %c marks to (new mark): " old) + (read-char)))) + (list old new))) + (when (eq new ?\n) + (error "Mark character \\n is not allowed")) + (let ((default-mark-p (equal tablist-marker-char new)) + (tablist-marker-char old)) + (save-excursion + (tablist-map-over-marks + (lambda nil + (pcase new + (?D + (tablist-flag-forward 1)) + (_ + (let ((tablist-marker-char new) + (tablist-marked-face + (and default-mark-p + tablist-marked-face))) + (tablist-put-mark))))))))) + +(defun tablist-unmark-all-marks (&optional marks interactive) + "Remove all marks in MARKS. + +MARKS should be a string of mark characters to match and defaults +to all marks. Interactively, remove all marks, unless a prefix +arg was given, in which case ask about which ones to remove. +Give a message, if interactive is non-nil. + +Returns the number of unmarked marks." + (interactive + (list (if current-prefix-arg + (read-string "Remove marks: ")) t)) + (let ((re (if marks + (tablist-marker-regexp marks) + "^[^ ]")) + (removed 0)) + (save-excursion + (goto-char (point-min)) + (while (re-search-forward re nil t) + (let ((tablist-marker-char ?\s) + tablist-marker-face + tablist-marked-face) + (tablist-put-mark)) + (cl-incf removed))) + (when interactive + (message "Removed %d marks" removed)) + removed)) + +(defun tablist-toggle-marks () + "Unmark all marked and mark all unmarked entries. + +See `tablist-put-mark'." + (interactive) + (let ((marked-re (tablist-marker-regexp)) + (not-marked-re + (let ((tablist-marker-char ?\s)) + (tablist-marker-regexp)))) + (save-excursion + (goto-char (point-min)) + (tablist-skip-invisible-entries) + (while (not (eobp)) + (cond + ((looking-at marked-re) + (save-excursion (tablist-unmark-backward -1))) + ((looking-at not-marked-re) + (tablist-put-mark))) + (tablist-forward-entry))) + (tablist-move-to-major-column))) + +(defun tablist-get-marked-items (&optional arg distinguish-one-marked) + "Return marked or ARG entries." + (let ((items (save-excursion + (tablist-map-over-marks + (lambda () (cons (tabulated-list-get-id) + (tabulated-list-get-entry))) + arg nil distinguish-one-marked)))) + (if (and distinguish-one-marked + (eq (car items) t)) + items + (nreverse items)))) + +(defun tablist-mark-items-regexp (column-name regexp) + "Mark entries matching REGEXP in column COLUMN-NAME." + (interactive + (tablist-read-regexp-filter "Mark" current-prefix-arg )) + (tablist-map-with-filter + 'tablist-put-mark + `(=~ ,column-name ,regexp))) + +(defun tablist-mark-items-numeric (binop column-name operand) + "Mark items fulfilling BINOP with arg OPERAND in column COLUMN-NAME. + +First the column's value is coerced to a number N. Then the test +proceeds as \(BINOP N OPERAND\)." + (interactive + (tablist-read-numeric-filter "Mark" current-prefix-arg)) + (tablist-map-with-filter + 'tablist-put-mark + `(,binop ,column-name ,operand))) + +(defun tablist-map-over-marks (fn &optional arg show-progress + distinguish-one-marked) + (prog1 + (cond + ((and arg (integerp arg)) + (let (results) + (tablist-repeat-over-lines + arg + (lambda () + (if show-progress (sit-for 0)) + (push (funcall fn) results))) + (if (< arg 0) + (nreverse results) + results))) + (arg + ;; non-nil, non-integer ARG means use current item: + (tablist-skip-invisible-entries) + (unless (eobp) + (list (funcall fn)))) + (t + (cl-labels ((search (re) + (let (success) + (tablist-skip-invisible-entries) + (while (and (setq success + (re-search-forward re nil t)) + (invisible-p (point))) + (tablist-forward-entry)) + success))) + (let ((regexp (tablist-marker-regexp)) + next-position results found) + (save-excursion + (goto-char (point-min)) + ;; remember position of next marked file before BODY + ;; can insert lines before the just found file, + ;; confusing us by finding the same marked file again + ;; and again and... + (setq next-position (and (search regexp) + (point-marker)) + found (not (null next-position))) + (while next-position + (goto-char next-position) + (if show-progress (sit-for 0)) + (push (funcall fn) results) + ;; move after last match + (goto-char next-position) + (forward-line 1) + (set-marker next-position nil) + (setq next-position (and (search regexp) + (point-marker))))) + (if (and distinguish-one-marked (= (length results) 1)) + (setq results (cons t results))) + (if found + results + (unless (or (eobp) (invisible-p (point))) + (list (funcall fn)))))))) + (tablist-move-to-major-column))) + +(defun tablist-marker-regexp (&optional marks) + "Return a regexp matching marks in MARKS. + +MARKS should be a string of mark characters to match and defaults +to the current value of `tablist-marker-char' as a string." + (concat (format "^[%s]" + (or marks (string tablist-marker-char))))) + +(defun tablist-get-mark-state () + "Return the mark state of the entry at point." + (save-excursion + (beginning-of-line) + (when (looking-at "^\\([^ ]\\)") + (let ((mark (buffer-substring + (match-beginning 1) + (match-end 1)))) + (tablist-move-to-major-column) + (list (aref mark 0) + (get-text-property 0 'face mark) + (get-text-property (point) 'face)))))) + +(defun tablist-put-mark-state (state) + "Set the mark of the entry at point according to STATE. + +STATE is a return value of `tablist-get-mark-state'." + (cl-destructuring-bind (tablist-marker-char + tablist-marker-face + tablist-marked-face) + state + (tablist-put-mark))) + +(defun tablist-mark-prompt (arg items) + "Return a string suitable for use in a tablist prompt." + ;; distinguish-one-marked can cause the first element to be just t. + (if (eq (car items) t) (setq items (cdr items))) + (let ((count (length items))) + (if (= count 1) + (car items) + ;; more than 1 item: + (if (integerp arg) + ;; abs(arg) = count + ;; Perhaps this is nicer, but it also takes more screen space: + ;;(format "[%s %d items]" (if (> arg 0) "next" "previous") + ;; count) + (format "[next %d item%s]" + arg (dired-plural-s arg)) + (format "%c [%d item%s]" dired-marker-char count + (dired-plural-s count)))))) + +;; +;; *Movement +;; + +(defun tablist-forward-entry (&optional n) + "Move past the next N unfiltered entries." + (unless n (setq n 1)) + (while (and (> n 0) + (not (eobp))) + (forward-line) + (when (invisible-p (point)) + (tablist-skip-invisible-entries)) + (cl-decf n)) + (while (and (< n 0) + (not (bobp))) + (forward-line -1) + (when (invisible-p (point)) + (tablist-skip-invisible-entries t)) + (cl-incf n)) + n) + +(defun tablist-next-line (&optional n) + (interactive "p") + (when (and (< n 0) + (save-excursion + (end-of-line 0) + (tablist-skip-invisible-entries t) + (bobp))) + (signal 'beginning-of-buffer nil)) + (when (and (> n 0) + (save-excursion + (tablist-forward-entry) + (eobp))) + (signal 'end-of-buffer nil)) + + (let ((col (tablist-current-column))) + (tablist-forward-entry (or n 1)) + (if col + (tablist-move-to-column col) + (tablist-move-to-major-column)))) + +(defun tablist-previous-line (&optional n) + (interactive "p") + (tablist-next-line (- (or n 1)))) + +(defun tablist-repeat-over-lines (arg function) + "Call FUNCTION for the next ARG entries." + ;; Move out of potentially invisble area. + (tablist-skip-invisible-entries) + (let ((pos (make-marker))) + (while (and (> arg 0) + (not (eobp))) + (cl-decf arg) + (save-excursion + (tablist-forward-entry) + (move-marker pos (1+ (point)))) + (unless (eobp) + (save-excursion (funcall function))) + ;; Advance to the next line--actually, to the line that *was* next. + ;; (If FUNCTION inserted some new lines in between, skip them.) + (goto-char pos)) + (while (and (< arg 0) (not (bobp))) + (cl-incf arg) + (tablist-forward-entry -1) + (save-excursion (funcall function))) + (move-marker pos nil) + (tablist-move-to-major-column))) + +(defun tablist-move-to-column (n) + "Move to the N'th list column." + (interactive "p") + (when (tabulated-list-get-id) + (let ((columns (tablist-column-offsets))) + (when (or (< n 0) + (>= n (length columns))) + (error "No such column: %s" n)) + (beginning-of-line) + (forward-char (nth n columns)) + (when (and (plist-get (nthcdr 3 (elt tabulated-list-format n)) + :right-align) + (not (= n (1- (length columns))))) + (forward-char (1- (car (cdr (elt tabulated-list-format n))))))))) + +(defun tablist-move-to-major-column (&optional first-skip-invisible-p) + "Move to the first major column." + (interactive (list t)) + (when first-skip-invisible-p + (tablist-skip-invisible-entries)) + (tablist-move-to-column (car (tablist-major-columns)))) + +(defun tablist-forward-column (n) + "Move n columns forward, while wrapping around." + (interactive "p") + (unless (tabulated-list-get-id) + (error "No entry on this line")) + (let* ((columns (tablist-column-offsets)) + (current (1- (length columns)))) + ;; find current column + (while (and (>= current 0) + (> (nth current columns) + (current-column))) + (cl-decf current)) + ;; there may be an invisible spec here + (when (bolp) + (forward-char)) + ;; before any columns + (when (< current 0) + (goto-char (+ (point-at-bol) (if (> n 0) + (car columns) + (car (last columns))))) + (setq n (* (cl-signum n) (1- (abs n))))) + (when (/= n 0) + (tablist-move-to-column + (mod (+ current n) (length columns)))))) + +(defun tablist-backward-column (n) + "Move n columns backward, while wrapping around." + (interactive "p") + (tablist-forward-column (- n))) + +;; +(defun tablist-skip-invisible-entries (&optional backward) + "Skip invisible entries BACKWARD or forward. + +Do nothing, if the entry at point is visible. Otherwise move to +the beginning of the next visible entry in the direction +determined by BACKWARD. + +Return t, if point is now in a visible area." + + (cond + ((and backward + (not (bobp)) + (get-text-property (point) 'invisible)) + (when (get-text-property (1- (point)) 'invisible) + (goto-char (previous-single-property-change + (point) + 'invisible nil (point-min)))) + (forward-line -1)) + ((and (not backward) + (not (eobp)) + (get-text-property (point) 'invisible)) + (goto-char (next-single-property-change + (point) + 'invisible nil (point-max))))) + (not (invisible-p (point)))) + +;; +;; *Operations +;; + +(defun tablist-yes-or-no-p (operation arg items) + "Query the user whether to proceed with some operation. + +Operation should be a symbol or string describing the operation, +ARG the prefix-arg of the command used in +`tablist-get-marked-items' or elsewhere, to get the ITEMS." + + (let ((pp-items (mapcar 'tablist-pretty-print-entry + (mapcar 'cdr items))) + dired-no-confirm + (op-str (upcase-initials + (if (stringp operation) + operation + (symbol-name operation))))) + (dired-mark-pop-up + (format " *%s*" op-str) nil + pp-items + dired-deletion-confirmer + (format "%s %s " + op-str + (tablist-mark-prompt arg pp-items))))) + +(defun tablist-operation-available-p (op) + (and (functionp tablist-operations-function) + (memq op (funcall tablist-operations-function + 'supported-operations)))) + +(defun tablist-do-delete (&optional arg) + "Delete ARG entries." + (interactive "P") + (unless (tablist-operation-available-p 'delete) + (error "Deleting entries is not available in this buffer")) + (let ((items (tablist-get-marked-items arg))) + (when (tablist-yes-or-no-p 'delete arg items) + (tablist-do-kill-lines arg) + (funcall tablist-operations-function + 'delete (mapcar 'car items)) + (tablist-move-to-major-column)))) + +(defun tablist-do-flagged-delete (&optional interactive) + "Delete all entries marked with a D." + (interactive "p") + (let* ((tablist-marker-char ?D)) + (if (save-excursion + (goto-char (point-min)) + (re-search-forward (tablist-marker-regexp) nil t)) + (tablist-do-delete) + (or (not interactive) + (message "(No deletions requested)"))))) + +(defun tablist-do-kill-lines (&optional arg interactive) + "Remove ARG lines from the display." + (interactive (list current-prefix-arg t)) + (save-excursion + (let ((positions + (tablist-map-over-marks 'point arg)) + (inhibit-read-only t)) + (dolist (pos positions) + (goto-char pos) + (tabulated-list-delete-entry)) + (when interactive + (message (format "Killed %d line%s" + (length positions) + (dired-plural-s (length positions)))))))) + +(defun tablist-do-operation (arg fn operation &optional delete-p revert-p) + "Operate on marked items. + +ARG should be the `current-prefix-arg', FN is a function of two +arguments \(ID ENTRY\) handling the operation. It gets called +repeatedly with all marked items. OPERATION is a symbol or string +describing the operation, it is used for display. + +Optional non-nil DELETE-P means, remove the items from the display. +Optional REVERT-P means, revert the display afterwards." + (let ((items (tablist-get-marked-items arg))) + (unless items + (error "No items marked")) + (when (tablist-yes-or-no-p operation arg items) + (when delete-p + (tablist-do-kill-lines arg)) + (dolist (item items) + (funcall fn (car item))) + (when revert-p + (tablist-revert)) + (tablist-move-to-major-column)))) + +;; +;; *Editing +;; +(defvar tablist-edit-column-minor-mode-map + (let ((kmap (make-sparse-keymap))) + (set-keymap-parent kmap (current-global-map)) + (define-key kmap [remap self-insert-command] #'self-insert-command) + (define-key kmap "\r" #'tablist-edit-column-commit) + (define-key kmap (kbd "C-g") #'tablist-edit-column-quit) + (define-key kmap (kbd "C-c C-c") #'tablist-edit-column-commit) + (define-key kmap (kbd "C-c C-q") #'tablist-edit-column-quit) + (define-key kmap "\t" #'tablist-edit-column-complete) + (define-key kmap (kbd "TAB") #'tablist-edit-column-complete) + (define-key kmap [remap end-of-buffer] #'end-of-line) + (define-key kmap [remap beginning-of-buffer] #'beginning-of-line) + (define-key kmap [remap mark-whole-buffer] #'tablist-edit-column-mark-field) + kmap)) + +(define-minor-mode tablist-edit-column-minor-mode + "" nil nil nil + (unless (or tablist-minor-mode + (derived-mode-p 'tablist-mode)) + (error "Not in a tablist buffer")) + (cond + (tablist-edit-column-minor-mode + (add-to-list 'mode-line-misc-info + '(tablist-edit-column-minor-mode "[edit]")) + (add-hook 'post-command-hook 'tablist-edit-column-constrain-point nil t) + (read-only-mode -1)) + (t + (remove-hook 'post-command-hook 'tablist-edit-column-constrain-point t) + (read-only-mode 1)))) + +(defun tablist-edit-column (&optional n) + (interactive "P") + (unless n (setq n (tablist-current-column))) + (tablist-assert-column-editable n) + (let* ((offsets (append (tablist-column-offsets) + (list (- (point-at-eol) + (point-at-bol))))) + (beg (+ (point-at-bol) + (nth n offsets))) + (end (+ (point-at-bol) + (nth (1+ n) offsets))) + (entry (tabulated-list-get-entry beg)) + (inhibit-read-only t) + (inhibit-field-text-motion t) + (alist `((entry . ,entry) + (column . ,n) + (id . ,(tabulated-list-get-id beg)))) + ov) + (goto-char beg) + (delete-region beg end) + (add-text-properties + (point-at-bol) (point-at-eol) + '(read-only t field t)) + (unless (= beg (point-at-bol)) + (put-text-property (1- beg) beg 'rear-nonsticky t)) + (save-excursion + ;; Keep one read-only space at the end for keeping text + ;; properties. + (insert + (propertize + (concat + (tablist-nth-entry n entry) + (propertize " " + 'display `(space :align-to ,(- end (point-at-bol))))) + 'field nil + 'front-sticky '(tablist-edit) + 'rear-nonsticky '(read-only field) + 'tablist-edit alist)) + (setq end (point))) + (add-text-properties + (1- end) end '(read-only t field 'tablist-edit-end)) + (setq ov (make-overlay beg end)) + (overlay-put ov 'priority 9999) + (overlay-put ov 'face '(:background "deep sky blue" :foreground "white")) + (overlay-put ov 'evaporate t) + (overlay-put ov 'tablist-edit t) + (tablist-edit-column-minor-mode 1))) + +(defun tablist-edit-column-quit () + (interactive) + (tablist-edit-column-commit t)) + +(defun tablist-edit-column-commit (&optional abandon-edit) + (interactive (list current-prefix-arg)) + (let ((inhibit-read-only t) + (inhibit-field-text-motion t) + bounds) + (condition-case nil + (setq bounds (tablist-edit-column-bounds)) + (error + (tablist-edit-column-minor-mode -1) + (tabulated-list-revert) + (put-text-property (point-min) (point-max) + 'tablist-edit nil) + (error "Unable to complete the edit"))) + (let* ((beg (car bounds)) + (end (cdr bounds)) + (alist (get-text-property beg 'tablist-edit)) + (column (cdr (assq 'column alist))) + (id (cdr (assq 'id alist))) + (entry (cdr (assq 'entry alist))) + (item (buffer-substring-no-properties beg (1- end)))) + + (unless abandon-edit + ;; Throws an error, if item is invalid. + (setq entry (funcall tablist-operations-function + 'edit-column id column item))) + (tablist-edit-column-minor-mode -1) + (remove-overlays beg end 'tablist-edit t) + (put-text-property beg end 'tablist-edit nil) + (delete-region (point-at-bol) (1+ (point-at-eol))) + (save-excursion + (tabulated-list-print-entry id entry)) + (forward-char (nth column (tablist-column-offsets)))))) + +(defun tablist-edit-column-complete () + (interactive) + (unless (tablist-operation-available-p 'complete) + (error "Completion not available")) + (cl-destructuring-bind (beg &rest end) + (tablist-edit-column-bounds t) + (let* ((string (buffer-substring-no-properties + beg end)) + (alist (get-text-property beg 'tablist-edit)) + (completions (funcall tablist-operations-function + 'complete + (cdr (assq 'id alist)) + (cdr (assq 'column alist)) + string + (- (point) beg)))) + (unless completions + (error "No completions available")) + (completion-in-region beg end completions)))) + +(defun tablist-column-editable (n) + (and (tablist-operation-available-p 'edit-column) + (not (tablist-column-property n :read-only)))) + +(defun tablist-assert-column-editable (n) + (unless (and (>= n 0) + (< n (length tabulated-list-format))) + (error "Invalid column number: %s" n)) + (unless (tablist-operation-available-p 'edit-column) + (error "Editing columns not enabled in this buffer")) + (when (tablist-column-property n :read-only) + (error "This column is read-only"))) + +(defun tablist-edit-column-constrain-point () + (unless tablist-edit-column-minor-mode + (error "Not editing a column")) + (unless (get-text-property (point) 'tablist-edit) + (let ((bounds (tablist-edit-column-bounds))) + (when bounds + (if (> (point) (cdr bounds)) + (goto-char (1- (cdr bounds))) + (goto-char (car bounds))) + (point))))) + +(defun tablist-edit-column-bounds (&optional skip-final-space) + (unless tablist-edit-column-minor-mode + (error "Not editing a column")) + (let ((pos (next-single-property-change + (point) 'tablist-edit)) + beg end) + (cond + ((null pos) + (setq end (previous-single-property-change + (point-max) 'tablist-edit) + beg (previous-single-property-change + end 'tablist-edit))) + ((get-text-property pos 'tablist-edit) + (setq beg pos + end (next-single-property-change + pos 'tablist-edit))) + (pos + (setq end pos + beg (previous-single-property-change + pos 'tablist-edit)))) + + (unless (and beg end (get-text-property beg 'tablist-edit)) + (error "Unable to locate edited text")) + (cons beg (if skip-final-space (1- end) end)))) + +(defun tablist-edit-column-mark-field () + (interactive) + (push-mark (field-beginning)) + (push-mark (field-end) nil t) + (goto-char (field-beginning))) + +(defun tablist-find-entry (&optional id) + (interactive) + (unless (tablist-operation-available-p 'find-entry) + (error "Finding entries not supported in this buffer")) + (funcall tablist-operations-function + 'find-entry + (or id (tabulated-list-get-id)))) + +;; +;; *Utility +;; + +(defun tablist-column-property (n prop) + (plist-get + (nthcdr 3 (aref tabulated-list-format n)) + prop)) + +(defun tablist-current-column () + "Return the column number at point. + +Returns nil, if point is before the first column." + (let ((column + (1- (cl-position + (current-column) + (append (tablist-column-offsets) + (list most-positive-fixnum)) + :test (lambda (column offset) (> offset column)))))) + (when (>= column 0) + column))) + +(defun tablist-column-offsets () + "Return a list of column positions. + +This is a list of offsets from the beginning of the line." + (let ((cc tabulated-list-padding) + columns) + (dotimes (i (length tabulated-list-format)) + (let* ((c (aref tabulated-list-format i)) + (len (nth 1 c)) + (pad (or (plist-get (nthcdr 3 c) :pad-right) + 1))) + (push cc columns) + (when (numberp len) + (cl-incf cc len)) + (when pad + (cl-incf cc pad)))) + (nreverse columns))) + +(defun tablist-pretty-print-entry (item) + (mapconcat (lambda (i) + (tablist-nth-entry i item)) + (tablist-major-columns) " ")) + +(defun tablist--save-face-property (beg end) + ;; We need to distinguish ,,not set'' from ''no face''. + (unless (and (text-property-not-all beg end 'face nil) + (< beg end)) + (put-text-property beg (1+ beg) 'face 'default)) + (unless (text-property-not-all beg end 'tablist-saved-face nil) + (tablist-copy-text-property beg end 'face 'tablist-saved-face))) + +(defun tablist--restore-face-property (beg end) + (when (text-property-not-all beg end 'tablist-saved-face nil) + (tablist-copy-text-property beg end 'tablist-saved-face 'face))) + +(defun tablist-copy-text-property (beg end from to) + "Copy text property FROM to TO in region BEG to END." + (let ((inhibit-read-only t)) + (save-excursion + (while (< beg end) + (goto-char beg) + (put-text-property + (point) + (setq beg (next-single-property-change + (point) from nil end)) + to + (get-text-property (point) from)))))) + +;; +(defun tablist-read-column-name (arg &optional prompt default) + "Read the name of a column using ARG. + +If ARG is a number, return column ARG. +If ARG is nil, return DEFAULT or the current column. +Else ask the user, using PROMPT and DEFAULT." + (cond + ((numberp arg) + (or (tablist-column-name + (prefix-numeric-value arg)) + (error "No such column: %d" (prefix-numeric-value arg)))) + ((null arg) + (or default + (tablist-column-name + (or (tablist-current-column) + (car (tablist-major-columns)) + 0)))) + (t + (let* ((default (or default + (tablist-column-name + (car (tablist-major-columns))))) + (result + (completing-read + (format "%s %s: " + (or prompt "Use column") + (if default + (format "(default %s)" + default) + "")) + (tablist-column-names) + nil t nil 'tablist-column-name-history))) + (if (> (length result) 0) + result + (if (not default) + (error "No column selected") + default)))))) + +(defun tablist-column-name (n) + "Return the name of column N." + (when (and n + (>= n 0) + (< n (length tabulated-list-format))) + (substring-no-properties + (car (elt tabulated-list-format n)) 0))) + +(defun tablist-column-names () + "Return a list of all column-names." + (mapcar 'tablist-column-name + (number-sequence 0 (1- (length tabulated-list-format))))) + +(defun tablist-nth-entry (n &optional entry) + (unless entry (setq entry (tabulated-list-get-entry))) + (when (and entry + (>= n 0) + (< n (length entry))) + (let ((str (elt entry n))) + (if (stringp str) + str + (car str))))) + +(defun tablist-major-column-name () + "Return a list of the major column names." + (tablist-column-name (car (tablist-major-columns)))) + +(defun tablist-export-csv (&optional separator always-quote-p + invisible-p out-buffer display-p) + "Export a tabulated list to a CSV format. + +Use SEPARATOR (or ;) and quote if necessary (or always if +ALWAYS-QUOTE-P is non-nil). Only consider non-filtered entries, +unless invisible-p is non-nil. Create a buffer for the output or +insert it after point in OUT-BUFFER. Finally if DISPLAY-P is +non-nil, display this buffer. + +Return the output buffer." + + (interactive (list nil t nil nil t)) + (unless (derived-mode-p 'tabulated-list-mode) + (error "Not in Tabulated List Mode")) + (unless (stringp separator) + (setq separator (string (or separator ?\;)))) + (let* ((outb (or out-buffer + (get-buffer-create + (format "%s.csv" (buffer-name))))) + (escape-re (format "[%s\"\n]" separator)) + (header (tablist-column-names))) + (unless (buffer-live-p outb) + (error "Expected a live buffer: %s" outb)) + (cl-labels + ((printit (entry) + (insert + (mapconcat + (lambda (e) + (unless (stringp e) + (setq e (car e))) + (if (or always-quote-p + (string-match escape-re e)) + (concat "\"" + (replace-regexp-in-string "\"" "\"\"" e t t) + "\"") + e)) + entry separator)) + (insert ?\n))) + (with-current-buffer outb + (let ((inhibit-read-only t)) + (erase-buffer) + (printit header))) + (save-excursion + (goto-char (point-min)) + (unless invisible-p + (tablist-skip-invisible-entries)) + (while (not (eobp)) + (let* ((entry (tabulated-list-get-entry))) + (with-current-buffer outb + (let ((inhibit-read-only t)) + (printit entry))) + (if invisible-p + (forward-line) + (tablist-forward-entry))))) + (if display-p + (display-buffer outb)) + outb))) + +;; + +(defun tablist-enlarge-column (&optional column width) + "Enlarge column COLUMN by WIDTH. + +This function is lazy and therefore pretty slow." + (interactive + (list nil (* (prefix-numeric-value current-prefix-arg) + 3))) + (unless column (setq column (tablist-current-column))) + (unless column + (error "No column given and no entry at point")) + (unless width (setq width 1)) + (when (or (not (numberp column)) + (< column 0) + (>= column (length tabulated-list-format))) + (error "No such column: %d" column)) + (when (= column (1- (length tabulated-list-format))) + (error "Can't resize last column")) + + (let* ((cur-width (cadr (elt tabulated-list-format column)))) + (setcar (cdr (elt tabulated-list-format column)) + (max 3 (+ cur-width width))) + (tablist-with-remembering-entry + (tablist-save-marks + (tabulated-list-init-header) + (tabulated-list-print))))) + +(defun tablist-shrink-column (&optional column width) + (interactive + (list nil (* (prefix-numeric-value current-prefix-arg) + 3))) + (tablist-enlarge-column column (- (or width 1)))) + +;; *Sorting +;; + +(defun tablist-sort (&optional column) + "Sort the tabulated-list by COLUMN. + +COLUMN may be either a name or an index. The default compare +function is given by the `tabulated-list-format', which see. + +This function saves the current sort column and the inverse +sort-direction in the variable `tabulated-list-sort-key', which +also determines the default COLUMN and direction. + +The main difference to `tabulated-list-sort' is, that this +function sorts the buffer in-place and it ignores a nil sort +entry in `tabulated-list-format' and sorts on the column +anyway (why not ?)." + + (interactive + (list + (if (null current-prefix-arg) + (tablist-column-name + (or (tablist-current-column) + (car (tablist-major-columns)) + 0)) + (tablist-read-column-name + '(4) "Sort by column" + (tablist-column-name (car (tablist-major-columns))))))) + + (unless column + (setq column (or (car tabulated-list-sort-key) + (tablist-column-name (car (tablist-major-columns))) + (tablist-column-name 0)))) + (when (numberp column) + (let ((column-name (tablist-column-name column))) + (unless column-name + (error "No such column: %d" column)) + (setq column column-name))) + + (setq tabulated-list-sort-key + (cons column + (if (equal column (car tabulated-list-sort-key)) + (cdr tabulated-list-sort-key)))) + + (let* ((entries (if (functionp tabulated-list-entries) + (funcall tabulated-list-entries) + tabulated-list-entries)) + (reverse (cdr tabulated-list-sort-key)) + (n (tabulated-list--column-number ;;errors if column is n/a + (car tabulated-list-sort-key))) + (compare-fn (nth 2 (aref tabulated-list-format n)))) + + (when (or (null compare-fn) + (eq compare-fn t)) + (setq compare-fn + (lambda (a b) + (setq a (aref (cadr a) n)) + (setq b (aref (cadr b) n)) + (string< (if (stringp a) a (car a)) + (if (stringp b) b (car b)))))) + + (unless compare-fn + (error "This column cannot be sorted: %s" column)) + + (setcdr tabulated-list-sort-key (not reverse)) + ;; Presort the entries and hash the result and sort the buffer. + (setq entries (sort (copy-sequence entries) compare-fn)) + (let ((hash (make-hash-table :test 'equal))) + (dotimes (i (length entries)) + (puthash (caar entries) i hash) + (setq entries (cdr entries))) + (tablist-with-remembering-entry + (goto-char (point-min)) + (tablist-skip-invisible-entries) + (let ((inhibit-read-only t)) + (sort-subr + nil 'tablist-forward-entry 'end-of-line + (lambda () + (gethash (tabulated-list-get-id) hash 0)) + nil (if reverse '< '>)))) + (tablist-move-to-column n) + ;; Make the sort arrows display. + (tabulated-list-init-header)))) + +;; +;; *Filter +;; + +(defun tablist-read-filter-name (prompt) + (let ((filter (cdr (assq major-mode tablist-named-filter)))) + (unless filter + (error "No filter defined for %s mode" mode-name)) + (let ((name (completing-read + (format "%s: " prompt) + filter + nil t))) + (unless (> (length name) 0) + (error "No filter selected")) + name))) + +(defun tablist-apply-filter (&optional filter) + "Apply FILTER to the current tabulated list. + +FILTER defaults to `tablist-current-filter'." + (unless filter (setq filter tablist-current-filter)) + (tablist-filter-unhide-buffer) + (when (and filter + (null tablist-filter-suspended)) + (tablist-with-remembering-entry + (tablist-map-with-filter + (lambda nil + (if tablist-umark-filtered-entries + (save-excursion (tablist-unmark-forward))) + (tablist-filter-hide-entry)) + (tablist-filter-negate filter)))) + (force-mode-line-update)) + +(defadvice tabulated-list-print (after tabulated-list activate) + "Reapply the filter." + (when (or tablist-minor-mode + (derived-mode-p 'tablist-mode)) + (tablist-apply-filter))) + +(defun tablist-eval-filter (filter) + (tablist-filter-eval + filter + (tabulated-list-get-id) + (tabulated-list-get-entry) + (cdr (assq major-mode tablist-named-filter)))) + +(defun tablist-map-with-filter (fn filter &optional show-progress + distinguish-one-marked) + "Call FN for every unfiltered entry matching FILTER." + (prog1 + (cl-labels ((search () + (tablist-skip-invisible-entries) + (while (and (not (eobp)) + (not (tablist-eval-filter filter))) + (tablist-forward-entry)) + (unless (eobp) + (point-marker)))) + (let (next-position results) + (save-excursion + (goto-char (point-min)) + (setq next-position (search)) + (while next-position + (goto-char next-position) + (if show-progress (sit-for 0)) + (push (funcall fn) results) + ;; move after last match + (goto-char next-position) + (forward-line 1) + (set-marker next-position nil) + (setq next-position (search))) + (if (and distinguish-one-marked (= (length results) 1)) + (setq results (cons t results)))))))) + +;; +;; **Filter Commands +;; +(defun tablist-push-filter (filter &optional interactive or-p) + (setq tablist-current-filter + (tablist-filter-push + tablist-current-filter + filter or-p)) + (tablist-apply-filter) + (when interactive + (tablist-display-filter-temporarily))) + +(defun tablist-pop-filter (&optional n interactive) + "Remove the first N filter components." + (interactive (list (prefix-numeric-value current-prefix-arg) t)) + (while (and tablist-current-filter + (> n 0)) + (setq tablist-current-filter + (tablist-filter-pop + tablist-current-filter)) + (cl-decf n)) + (tablist-apply-filter) + (when interactive + (when (> n 0) + (message "The filter is empty.")) + (tablist-display-filter-temporarily)) + n) + +(defun tablist-negate-filter (&optional interactive) + "Negate the current filter." + (interactive (list t)) + (setq tablist-current-filter + (tablist-filter-negate + tablist-current-filter)) + (tablist-apply-filter) + (when interactive + (tablist-display-filter-temporarily))) + +(defun tablist-toggle-first-filter-logic () + "Toggle between and/or for the first filter operand." + (interactive) + (setq tablist-current-filter + (pcase tablist-current-filter + (`(or ,x1 ,x2) + `(and ,x1 ,x2)) + (`(and ,x1 ,x2) + `(or ,x1 ,x2)) + (`(not ,x) x) + (x `(not ,x)))) + (tablist-apply-filter) + (tablist-display-filter-temporarily)) + +(defun tablist-suspend-filter (&optional flag) + "Temporarily disable filtering according to FLAG. + +Interactively, this command toggles filtering." + (interactive + (list (not tablist-filter-suspended))) + (let ((state tablist-filter-suspended)) + (unless (eq (not (not state)) + (not (not flag))) + (set (make-local-variable 'tablist-filter-suspended) flag) + (tablist-apply-filter)))) + +(defun tablist-read-regexp-filter (operation arg) + (let ((column-name (tablist-read-column-name arg))) + (list + column-name + (let ((re + (read-regexp (format "%s where %s matches: " operation column-name)))) + (unless (> (length re) 0) + (error "No regexp given")) + re)))) + +(defun tablist-read-equal-filter (operation arg) + (let ((column-name (tablist-read-column-name arg))) + (list + column-name + (read-string (format "%s where %s equals: " operation column-name))))) + +(defun tablist-read-numeric-filter (operation arg) + (let* ((entry (tabulated-list-get-entry 1)) + (default (cl-some + (lambda (idx) + (let ((value (tablist-nth-entry idx entry))) + (when (or (not (eq 0 (string-to-number value))) + (equal "0" value)) + (tablist-column-name idx)))) + (number-sequence 0 (length entry)))) + (column-name (tablist-read-column-name arg nil default)) + (op (completing-read + (format "%s %s matching binary op: " operation column-name) + '("=" "<" ">" "<=" ">=") nil t)) + oper) + + (when (equal "" op) + (error "No operation selected")) + (setq op (intern op)) + (setq oper (number-to-string + (read-number + (format "%s where %s %s " operation column-name op)))) + + (list op column-name oper))) + +(defun tablist-push-regexp-filter (column-name regexp) + "Add a new filter matching REGEXP in COLUMN-NAME. + +The filter is and'ed with the current filter. Use +`tablist-toggle-first-filter-logic' to change this." + (interactive + (tablist-with-filter-displayed + (tablist-read-regexp-filter "Filter" current-prefix-arg))) + (tablist-push-filter + `(=~ ,column-name ,regexp) + (called-interactively-p 'any))) + +(defun tablist-push-equal-filter (column-name string) + "Add a new filter whre string equals COLUMN-NAME's value. + +The filter is and'ed with the current filter. Use +`tablist-toggle-first-filter-logic' to change this." + (interactive + (tablist-with-filter-displayed + (tablist-read-equal-filter "Filter" current-prefix-arg))) + (tablist-push-filter + `(== ,column-name ,string) + (called-interactively-p 'any))) + +(defun tablist-push-numeric-filter (op column-name 2nd-arg) + "Add a new filter matching a numeric predicate. + +The filter is and'ed with the current filter. Use +`tablist-toggle-first-filter-logic' to change this." + (interactive + (tablist-with-filter-displayed + (tablist-read-numeric-filter "Filter" current-prefix-arg))) + (tablist-push-filter + `(,op ,column-name ,2nd-arg) + (called-interactively-p 'any))) + +(defun tablist-push-named-filter (name) + "Add a named filter called NAME. + +Named filter are saved in the variable `tablist-named-filter'." + (interactive + (tablist-with-filter-displayed + (list + (tablist-read-filter-name "Add filter")))) + (when (and name (symbolp name)) + (setq name (symbol-name name))) + (tablist-push-filter name (called-interactively-p 'any))) + +(defun tablist-delete-named-filter (name &optional mode) + (interactive + (tablist-with-filter-displayed + (list + (tablist-read-filter-name "Delete filter")))) + (setq tablist-current-filter + (tablist-filter-map + (lambda (f) + (when (equal f name) + (setq f (tablist-get-named-filter f))) + f) + tablist-current-filter)) + (unless mode (setq mode major-mode)) + (let ((mode-filter + (assq mode tablist-named-filter))) + (when mode-filter + (setcdr mode-filter + (cl-remove name (cdr mode-filter) + :test 'equal :key 'car))))) + +(defun tablist-name-current-filter (name) + (interactive + (list (tablist-with-filter-displayed + (read-string + "Add name for current filter: ")))) + (unless tablist-current-filter + (error "Filter is empty")) + (unless (> (length name) 0) + (error "No name given")) + (tablist-put-named-filter + name (if (stringp tablist-current-filter) + (tablist-get-named-filter + tablist-current-filter) + tablist-current-filter)) + (setq tablist-current-filter name) + (force-mode-line-update)) + +(defun tablist-deconstruct-named-filter () + (interactive) + (let (found) + (setq tablist-current-filter + (tablist-filter-map + (lambda (f) + (when (and (not found) + (stringp f)) + (setq found t) + (let ((df (tablist-get-named-filter f))) + (unless df + (error "Filter is not defined: %s" f)) + (setq f df))) + f) + tablist-current-filter)) + (unless found + (error "No named filter found")) + (force-mode-line-update))) + +(defun tablist-filter-names (&optional mode) + (mapcar 'car (cdr (assq (or mode major-mode) + tablist-named-filter)))) + +(defun tablist-get-named-filter (name &optional mode) + (cdr (assoc name + (cdr (assq (or mode major-mode) + tablist-named-filter))))) + +(defun tablist-put-named-filter (name filter &optional mode) + (unless mode (setq mode major-mode)) + (let ((mode-filter + (assq mode tablist-named-filter))) + (unless mode-filter + (setq mode-filter (cons mode nil)) + (push mode-filter tablist-named-filter)) + (let ((entry (assoc name mode-filter))) + (if entry + (setcdr entry filter) + (setcdr mode-filter + (list (cons name filter))))))) + +(defun tablist-validate-named-filter (filter) + (tablist-filter-map + (lambda (f) + (when (and (stringp f) + (null (tablist-get-named-filter f))) + (error "Undefined named filter: %s (defined: %s)" f + (mapconcat 'identity (tablist-filter-names) ",")))) + filter)) + +(defun tablist-edit-filter () + (interactive) + (setq tablist-current-filter + (tablist-with-filter-displayed + (tablist-filter-edit-filter + "Edit filter: " + tablist-current-filter + nil + 'tablist-validate-named-filter))) + (tablist-apply-filter)) + +(defun tablist-clear-filter () + (interactive) + (setq tablist-current-filter nil) + (tablist-apply-filter)) + +;; **Displaying filter +;; + +(defconst tablist-display-filter-mode-line-tag nil) + +(defun tablist-display-filter (&optional flag) + "Display the current filter according to FLAG. + +If FLAG has the value 'toggle, toggle it's visibility. +If FLAG has the 'state, then do nothing but return the current +visibility." + (interactive (list 'toggle)) + (let* ((tag 'tablist-display-filter-mode-line-tag) + (displayed-p (not (not (assq tag mode-line-format))))) + (if (eq flag 'state) + displayed-p + (let ((display-p (not (not (if (eq flag 'toggle) + (not displayed-p) + flag))))) + (unless (eq displayed-p display-p) + (setq mode-line-format + (if display-p + (list (cons tag mode-line-format) + '(:eval + (replace-regexp-in-string + "%" "%%" + (concat + (propertize "Filter: " 'face 'minibuffer-prompt) + (and tablist-filter-suspended + "[suspended] ") + (if tablist-current-filter + (tablist-filter-unparse + tablist-current-filter t) + "[none]"))))) + (cdr (assq tag mode-line-format))))) + (force-mode-line-update) + display-p)))) + +(defun tablist-display-filter-temporarily () + (tablist-with-filter-displayed + (sit-for 9999))) + +;; +;; **Hiding/Unhiding Entries +;; +(defun tablist-filter-set-entry-hidden (flag &optional pos) + (save-excursion + (when pos (goto-char pos)) + (beginning-of-line) + (let ((inhibit-read-only t)) + (add-text-properties + (point-at-bol) + (1+ (point-at-eol)) + `(invisible ,flag))))) + +(defun tablist-filter-hide-entry (&optional pos) + (interactive) + (tablist-filter-set-entry-hidden t pos)) + +(defun tablist-filter-unhide-entry (&optional pos) + (tablist-filter-set-entry-hidden nil pos)) + +(defun tablist-filter-unhide-buffer () + (let ((inhibit-read-only t)) + (remove-text-properties + (point-min) (point-max) + '(invisible)))) + +(defun tablist-window-attach (awindow &optional window) + "Attach AWINDOW to WINDOW. + +This has the following effect. Whenever WINDOW, defaulting to +the selected window, stops displaying the buffer it currently +displays (e.g., by switching buffers or because it was deleted) +AWINDOW is deleted." + (unless window (setq window (selected-window))) + (let ((buffer (window-buffer window)) + (hook (make-symbol "window-attach-hook"))) + (fset hook + (lambda () + (when (or (not (window-live-p window)) + (not (eq buffer (window-buffer window)))) + (remove-hook 'window-configuration-change-hook + hook) + ;; Deleting windows inside wcch may cause errors in + ;; windows.el . + (run-with-timer + 0 nil (lambda (win) + (when (and (window-live-p win) + (not (eq win (selected-window)))) + (delete-window win))) + awindow)))) + (add-hook 'window-configuration-change-hook hook))) + +(defun tablist-display-buffer-split-below-and-attach (buf alist) + "Display buffer action using `tablist-window-attach'." + (let ((window (selected-window)) + (height (cdr (assq 'window-height alist))) + newwin) + (when height + (when (floatp height) + (setq height (round (* height (frame-height))))) + (setq height (- (max height window-min-height)))) + (setq newwin (window--display-buffer + buf + (split-window-below height) + 'window alist)) + (tablist-window-attach newwin window) + newwin)) + +(defun tablist-generate-sorter (column compare-fn &optional read-fn) + "Generate a sort function for `tabulated-list' entries. + +Example: + + \(tablist-generate-sorter 0 '< 'string-to-number\) + +would create a sort function sorting `tabulated-list-entries' on +the 0-th column as numbers by the less-than relation." + + (lambda (e1 e2) + (funcall compare-fn + (funcall (or read-fn 'identity) + (aref (cadr e1) column)) + (funcall (or read-fn 'identity) + (aref (cadr e2) column))))) + +(provide 'tablist) +;; Local Variables: +;; outline-regexp: ";;\\(\\(?:[;*]+ \\| \\*+\\)[^\s\t\n]\\|###autoload\\)\\|(" +;; indent-tabs-mode: nil +;; End: +;;; tablist.el ends here diff --git a/elpa/tablist-20200427.2205/tablist.elc b/elpa/tablist-20200427.2205/tablist.elc Binary files differ. diff --git a/init.el b/init.el @@ -158,7 +158,7 @@ ("melpa-stable" . "https://stable.melpa.org/packages/") ("melpa" . "https://melpa.org/packages/"))) '(package-selected-packages - '(paredit-menu paredit vertico-posframe vertico corfu sly eglot aggressive-indent project nov nhexl-mode elfeed magit yaml-mode json-mode lua-mode go-mode geiser-guile geiser org-roam org-contrib org ace-window expand-region consult marginalia uuidgen request diminish which-key)) + '(pdf-tools paredit-menu paredit vertico-posframe vertico corfu sly eglot aggressive-indent project nov nhexl-mode elfeed magit yaml-mode json-mode lua-mode go-mode geiser-guile geiser org-roam org-contrib org ace-window expand-region consult marginalia uuidgen request diminish which-key)) '(pcomplete-ignore-case t) '(read-buffer-completion-ignore-case t) '(read-file-name-completion-ignore-case t)