From 7e00753126593a7d0a191aad1192154f75522288 Mon Sep 17 00:00:00 2001 From: KemoNine Date: Thu, 25 Aug 2022 11:49:25 -0400 Subject: [PATCH] added pdf-tools --- _setup.sh | 1 + org/elpa/pdf-tools-20220823.513/README | 660 ++ .../pdf-tools-20220823.513/build/Makefile | 103 + .../build/server/.gitignore | 25 + .../build/server/Makefile.am | 44 + .../build/server/autobuild | 585 ++ .../build/server/autogen.sh | 5 + .../build/server/configure.ac | 96 + .../build/server/epdfinfo.c | 3721 +++++++ .../build/server/epdfinfo.h | 246 + .../build/server/poppler-versions | 12 + .../build/server/synctex_parser.c | 8924 +++++++++++++++++ .../build/server/synctex_parser.h | 429 + .../build/server/synctex_parser_advanced.h | 554 + .../build/server/synctex_parser_local.h | 3 + .../build/server/synctex_parser_readme.txt | 204 + .../build/server/synctex_parser_utils.c | 570 ++ .../build/server/synctex_parser_utils.h | 163 + .../build/server/synctex_parser_version.txt | 1 + .../build/server/synctex_version.h | 59 + .../build/server/test/docker/.gitignore | 2 + .../build/server/test/docker/lib/run-tests | 9 + .../build/server/test/docker/lib/yes-or-enter | 9 + .../docker/templates/Dockerfile.common.in | 4 + .../templates/arch.Dockerfile.in.FAILING | 4 + .../templates/centos.Dockerfile.in.FAILING | 6 + .../docker/templates/debian-10.Dockerfile.in | 6 + .../docker/templates/debian-11.Dockerfile.in | 6 + .../docker/templates/debian-9.Dockerfile.in | 6 + .../docker/templates/fedora-34.Dockerfile.in | 4 + .../docker/templates/fedora-35.Dockerfile.in | 4 + .../docker/templates/fedora-36.Dockerfile.in | 4 + .../templates/freebsd.Dockerfile.in.FAILING | 0 .../templates/gentoo.Dockerfile.in.FAILING | 6 + .../templates/macos.Dockerfile.in.FAILING | 0 .../templates/msys2.Dockerfile.in.FAILING | 0 .../templates/nixos.Dockerfile.in.FAILING | 0 .../templates/openbsd.Dockerfile.in.FAILING | 0 .../templates/opensuse.Dockerfile.in.FAILING | 0 .../docker/templates/ubuntu-18.Dockerfile.in | 5 + .../docker/templates/ubuntu-20.Dockerfile.in | 5 + .../docker/templates/ubuntu-22.Dockerfile.in | 5 + .../templates/void.Dockerfile.in.FAILING | 0 org/elpa/pdf-tools-20220823.513/pdf-annot.el | 1880 ++++ org/elpa/pdf-tools-20220823.513/pdf-cache.el | 499 + org/elpa/pdf-tools-20220823.513/pdf-dev.el | 89 + .../pdf-tools-20220823.513/pdf-history.el | 172 + org/elpa/pdf-tools-20220823.513/pdf-info.el | 1745 ++++ .../pdf-tools-20220823.513/pdf-isearch.el | 832 ++ org/elpa/pdf-tools-20220823.513/pdf-links.el | 379 + org/elpa/pdf-tools-20220823.513/pdf-loader.el | 80 + org/elpa/pdf-tools-20220823.513/pdf-macs.el | 51 + org/elpa/pdf-tools-20220823.513/pdf-misc.el | 300 + org/elpa/pdf-tools-20220823.513/pdf-occur.el | 820 ++ .../pdf-tools-20220823.513/pdf-outline.el | 599 ++ org/elpa/pdf-tools-20220823.513/pdf-sync.el | 768 ++ .../pdf-tools-autoloads.el | 637 ++ .../pdf-tools-20220823.513/pdf-tools-pkg.el | 15 + org/elpa/pdf-tools-20220823.513/pdf-tools.el | 544 + org/elpa/pdf-tools-20220823.513/pdf-util.el | 1364 +++ org/elpa/pdf-tools-20220823.513/pdf-view.el | 1727 ++++ .../pdf-tools-20220823.513/pdf-virtual.el | 1039 ++ .../tablist-autoloads.el | 60 + .../tablist-20200427.2205/tablist-filter.el | 464 + org/elpa/tablist-20200427.2205/tablist-pkg.el | 11 + org/elpa/tablist-20200427.2205/tablist.el | 1945 ++++ org/init.el | 4 +- org/nov-places | 1 + 68 files changed, 32514 insertions(+), 1 deletion(-) create mode 100644 org/elpa/pdf-tools-20220823.513/README create mode 100644 org/elpa/pdf-tools-20220823.513/build/Makefile create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/.gitignore create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/Makefile.am create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/autobuild create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/autogen.sh create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/configure.ac create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/epdfinfo.c create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/epdfinfo.h create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/poppler-versions create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/synctex_parser.c create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/synctex_parser.h create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/synctex_parser_advanced.h create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/synctex_parser_local.h create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/synctex_parser_readme.txt create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/synctex_parser_utils.c create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/synctex_parser_utils.h create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/synctex_parser_version.txt create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/synctex_version.h create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/test/docker/.gitignore create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/test/docker/lib/run-tests create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/test/docker/lib/yes-or-enter create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/Dockerfile.common.in create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/arch.Dockerfile.in.FAILING create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/centos.Dockerfile.in.FAILING create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/debian-10.Dockerfile.in create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/debian-11.Dockerfile.in create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/debian-9.Dockerfile.in create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/fedora-34.Dockerfile.in create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/fedora-35.Dockerfile.in create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/fedora-36.Dockerfile.in create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/freebsd.Dockerfile.in.FAILING create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/gentoo.Dockerfile.in.FAILING create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/macos.Dockerfile.in.FAILING create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/msys2.Dockerfile.in.FAILING create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/nixos.Dockerfile.in.FAILING create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/openbsd.Dockerfile.in.FAILING create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/opensuse.Dockerfile.in.FAILING create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/ubuntu-18.Dockerfile.in create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/ubuntu-20.Dockerfile.in create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/ubuntu-22.Dockerfile.in create mode 100644 org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/void.Dockerfile.in.FAILING create mode 100644 org/elpa/pdf-tools-20220823.513/pdf-annot.el create mode 100644 org/elpa/pdf-tools-20220823.513/pdf-cache.el create mode 100644 org/elpa/pdf-tools-20220823.513/pdf-dev.el create mode 100644 org/elpa/pdf-tools-20220823.513/pdf-history.el create mode 100644 org/elpa/pdf-tools-20220823.513/pdf-info.el create mode 100644 org/elpa/pdf-tools-20220823.513/pdf-isearch.el create mode 100644 org/elpa/pdf-tools-20220823.513/pdf-links.el create mode 100644 org/elpa/pdf-tools-20220823.513/pdf-loader.el create mode 100644 org/elpa/pdf-tools-20220823.513/pdf-macs.el create mode 100644 org/elpa/pdf-tools-20220823.513/pdf-misc.el create mode 100644 org/elpa/pdf-tools-20220823.513/pdf-occur.el create mode 100644 org/elpa/pdf-tools-20220823.513/pdf-outline.el create mode 100644 org/elpa/pdf-tools-20220823.513/pdf-sync.el create mode 100644 org/elpa/pdf-tools-20220823.513/pdf-tools-autoloads.el create mode 100644 org/elpa/pdf-tools-20220823.513/pdf-tools-pkg.el create mode 100644 org/elpa/pdf-tools-20220823.513/pdf-tools.el create mode 100644 org/elpa/pdf-tools-20220823.513/pdf-util.el create mode 100644 org/elpa/pdf-tools-20220823.513/pdf-view.el create mode 100644 org/elpa/pdf-tools-20220823.513/pdf-virtual.el create mode 100644 org/elpa/tablist-20200427.2205/tablist-autoloads.el create mode 100644 org/elpa/tablist-20200427.2205/tablist-filter.el create mode 100644 org/elpa/tablist-20200427.2205/tablist-pkg.el create mode 100644 org/elpa/tablist-20200427.2205/tablist.el create mode 100644 org/nov-places diff --git a/_setup.sh b/_setup.sh index 9e36fd1..3fbd063 100755 --- a/_setup.sh +++ b/_setup.sh @@ -22,3 +22,4 @@ fi cat ~/.emacs-profiles.el echo "run emacs with each profile in '~/.emacs-profiles.el'" echo "install multimarkdown (windows bin release + add to emacs path like sqlite3)" +echo "pacman -Ss mingw-w64-x86_64-{emacs,emacs-pdf-tools-server,imagemagick} in msys2" diff --git a/org/elpa/pdf-tools-20220823.513/README b/org/elpa/pdf-tools-20220823.513/README new file mode 100644 index 0000000..7c8d01c --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/README @@ -0,0 +1,660 @@ +#+TITLE: PDF Tools README +#+AUTHOR: Andreas Politz +#+EMAIL: mail@andreas-politz.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://elpa.nongnu.org/nongnu/pdf-tools.html][http://elpa.nongnu.org/nongnu/pdf-tools.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]] + +The ~pdf-tools~ Wiki is maintained at https://pdftools.wiki. Head to the site if you find it easier to navigate a website for reading a manual. All the topics on the site are listed at https://pdftools.wiki/impulse. + +* About PDF Tools +:PROPERTIES: +:CREATED: [2021-12-29 Wed 18:34] +:ID: 5a884389-6aec-498a-90d5-f37168809b4f +:EXPORT_FILE_NAME: index +:END: +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. [[https://www.dailymotion.com/video/x2bc1is][Watch this video for a detailed demo!]] + +* Installing ~pdf-tools~ +:PROPERTIES: +:CREATED: [2021-12-29 Wed 18:34] +:ID: 6ceea50c-cbaa-4d8a-b450-8067c5e8c9da +:NEURON_DIRTREE_DISPLAY: false +:END: +Installing this package via NonGNU ELPA or MELPA or any of the other package managers is straightforward and should just work. +~pdf-tools~ requires a server ~epdfinfo~ to run against, which it will try to compile and build when it is activated for the first time. + +You should not require any manual changes. The documentation below is *only if you are installing from source*, or for troubleshooting / debugging purposes. The following steps need to be followed *in this order*, to install ~pdf-tools~ and ~epdfinfo~ correctly: + +- [[brain-child:8ce3cf4e-d186-4de1-a40e-f41063068ab0][Installing ~epdfinfo~ server prerequisites]] +- [[brain-child:e305cd0a-e798-4c2b-af27-21bcd936c1c9][Installing the ~epdfinfo~ server]] +- [[brain-child:3d4e6b6b-f015-475d-8ea2-84988efd6c22][Installing ~pdf-tools~ elisp prerequisites]] +- [[brain-child:32c4fc3b-b4ea-43bd-b92c-bdf2d3831fcf][Installing ~pdf-tools~ elisp code]] + +** Installing ~epdfinfo~ server prerequisites +:PROPERTIES: +:CREATED: [2021-12-29 Wed 18:34] +:ID: 8ce3cf4e-d186-4de1-a40e-f41063068ab0 +:END: +If you install ~pdf-tools~ via NonGNU ELPA or MELPA, *you don't need to worry about this separate server installation at all*. However, if you have a non-standard installation, please refer to the links below for installing ~epdfinfo~ server prerequisites. + +Note: You'll need GNU Emacs \ge 26.1 and some form of a GNU/Linux OS. Other operating systems are not officially supported, but ~pdf-tools~ is known to work on many of them. + +Similarly, package-managers are not officially supported, but ~pdf-tools~ is known to be available on some of them. See the section on [[id:fb5cef15-fed4-4dec-a443-52f7c00c7831][Installing the ~epdfinfo~ server from package managers]] to avoid manual installation of server / server prerequisites. + +See the section on [[id:A34704B9-1B51-4614-8806-C4059F7B42D5][I want to add support for ~pdf-tools~ on =My Fav OS=. How do I do that?]] to add your favorite Operating System to this list. + +*** Installing ~epdfinfo~ Server Prerequisites on a Debian-based system +:PROPERTIES: +:CREATED: [2022-02-13 Sun 23:17] +:ID: abaae1be-3bbb-4d99-90e7-5429c56083e1 +:END: +First make sure a suitable build-system is installed. We need at least a C compiler (~gcc~), ~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 apt install libpng-dev zlib1g-dev libpoppler-glib-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 apt install imagemagick +#+end_src +*** Installing ~epdfinfo~ Server Prerequisites On macOS +:PROPERTIES: +:CREATED: [2021-12-29 Wed 18:34] +:ID: f10e9d94-bdec-44dc-8d3c-1816d62ef1c4 +:END: +Although macOS is not officially supported, it has been reported that ~pdf-tools~ works well on macOS. 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~. ~brew~ will show you which paths need to be added to ~PKG_CONFIG_PATH~ during the installation process. Make sure you export the paths to the env variable, eg: +#+BEGIN_SRC sh + $ export PKG_CONFIG_PATH="${PKG_CONFIG_PATH}:$(brew --prefix libffi)/lib/pkgconfig/:/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. +*** Installing ~epdfinfo~ Server Prerequisites On FreeBSD +:PROPERTIES: +:CREATED: [2021-12-29 Wed 18:34] +:ID: 00faf3e3-6d09-4cf7-9373-838f3d231504 +:END: +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. +*** Installing ~epdfinfo~ Server Prerequisites On CentOS +:PROPERTIES: +:CREATED: [2021-12-29 Wed 18:34] +:ID: e39946d0-3a28-405d-bb23-337120412dac +:END: +#+BEGIN_SRC sh + $ yum install poppler-devel poppler-glib-devel +#+END_SRC + +*** Installing ~epdfinfo~ Server Prerequisites On Fedora +:PROPERTIES: +:CREATED: [2021-12-29 Wed 18:34] +:ID: d0013822-f4d0-4354-b3db-c54ffe41ce58 +:END: +#+BEGIN_SRC sh + $ sudo dnf install make automake autoconf gcc libpng-devel zlib-devel poppler-glib-devel +#+END_SRC + +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 dnf install imagemagick +#+end_src +*** Installing ~epdfinfo~ Server Prerequisites On openSUSE +:PROPERTIES: +:ID: 07033620-fee5-4b52-a99d-e62e4b758ccc +:CREATED: [2022-04-25 Mon 21:09] +:END: +For openSUSE Tumbleweed and Leap: +#+BEGIN_SRC sh + $ sudo zypper install make automake autoconf gcc libpng16-devel libpng16-compat-devel zlib-devel libpoppler-devel libpoppler-glib-devel glib2-devel +#+END_SRC + +For openSUSE MicroOS Desktop: +#+BEGIN_SRC sh + $ pkcon install make automake autoconf gcc libpng16-devel libpng16-compat-devel zlib-devel libpoppler-devel libpoppler-glib-devel glib2-devel +#+END_SRC + +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 the imagemagick package via the package manager of your choice. +*** Installing ~epdfinfo~ Server Prerequisites On Alpine Linux +:PROPERTIES: +:CREATED: [2021-12-29 Wed 18:34] +:ID: 443d9b18-096e-4770-b59c-4e472a5d4b0e +:END: +#+BEGIN_SRC sh + $ apk add build-base gcc automake autoconf libpng-dev glib-dev poppler-dev +#+END_SRC + +*** Installing ~epdfinfo~ Server Prerequisites On Windows +:PROPERTIES: +:CREATED: [2021-12-29 Wed 18:34] +:ID: 005243cb-1557-4f94-a73d-e647e0d4b53d +:END: +~pdf-tools~ can be built and used on Windows using the MSYS2 compiler, or pre-built binaries can be installed in MSYS2. + +The pre-built binaries will work with native (not Cygwin) Windows builds of Emacs. They include the standard binaries provided by the GNU project, those available as MSYS2 packages and numerous third-party binaries. Refer to the appropriate section under [[id:e305cd0a-e798-4c2b-af27-21bcd936c1c9][Installing the ~epdfinfo~ server]] for more details. + +~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. + +** Installing the ~epdfinfo~ server +:PROPERTIES: +:CREATED: [2021-12-29 Wed 18:34] +:ID: e305cd0a-e798-4c2b-af27-21bcd936c1c9 +:END: +If you install ~pdf-tools~ via NonGNU ELPA or MELPA, you don't need to worry about this separate server installation at all. However, if you have a non-standard installation, please refer to the links below for installing ~epdfinfo~. +*** Compiling and Installing the ~epdfinfo~ server from source on Linux +:PROPERTIES: +:CREATED: [2022-02-13 Sun 23:11] +:ID: bd7fd084-8fdf-4698-b40a-da75920d17ed +:END: +Note that this is the only officially supported method for installing the ~epdfinfo~ binary. Instructions: + +#+begin_src sh + $ cd /path/to/pdf-tools + $ make -s +#+end_src + +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. +*** Compiling and Installing the ~epdfinfo~ server from source on Windows +:PROPERTIES: +:CREATED: [2021-12-29 Wed 18:34] +:ID: d14e01ff-9bd5-47ee-86fc-859b4499d5d7 +: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, [[https://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/vedang/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. See the note on how to set up PATH in the previous section. + +*** Installing the ~epdfinfo~ server from package managers +:PROPERTIES: +:CREATED: [2022-02-13 Sun 23:10] +:ID: fb5cef15-fed4-4dec-a443-52f7c00c7831 +:END: +Note that the packages available on these package managers are not maintained by the author and might be outdated. +**** Using the pre-built MINGW packages from MSYS2 on Windows +:PROPERTIES: +:CREATED: [2022-02-13 Sun 22:55] +:ID: 1fc6e25b-ae09-45d7-8288-c57c7065326c +:END: +Package maintained at: https://packages.msys2.org/package/mingw-w64-x86_64-emacs-pdf-tools-server?repo=mingw64 + +Users installing Emacs from the MSYS2 distribution can install pre-built binaries of the ~epdfinfo~ server. + +1. [[https://www.msys2.org/][Install MSYS2]] and update the package database and core packages using the instructions provided. +2. Install packages: ~pacman -Ss mingw-w64-x86_64-{emacs,emacs-pdf-tools-server,imagemagick}~ (ImageMagick is optional, see above.) +3. Make sure Emacs can find ~epdfinfo.exe~. Either add the MINGW install location (e.g. ~C:/msys2/mingw64/bin~) to the system path with ~setx PATH "C:\msys2\mingw64\bin;%PATH%"~ or set Emacs's path with ~(setenv "PATH" (concat "C:\\msys64\\mingw64\\bin;" (getenv "PATH")))~. 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. +4. Add ~(pdf-tools-install)~ to your Emacs config. + +**** Using the pre-built packages from Debian +:PROPERTIES: +:CREATED: [2022-02-13 Sun 23:30] +:ID: 416af9e8-b437-4f6e-ac21-15b79822780e +:END: +Package maintained at: https://packages.debian.org/buster/elpa-pdf-tools-server +**** Using the pre-built packages from Ubuntu +:PROPERTIES: +:CREATED: [2022-02-13 Sun 23:31] +:ID: b2c49338-845f-421a-93f3-a3da5efcc4ac +:END: +Package maintained at: https://packages.ubuntu.com/impish/elpa-pdf-tools-server +** Installing ~pdf-tools~ elisp prerequisites +:PROPERTIES: +:CREATED: [2021-12-29 Wed 18:34] +:ID: 3d4e6b6b-f015-475d-8ea2-84988efd6c22 +:END: +This package depends on the following Elisp packages, which should be installed before installing the ~pdf-tools~ package. +| Package | Required version | +|-----------+----------------------------------| +| [[http://melpa.org/#/tablist][tablist]] | >= 0.70 | +|-----------+----------------------------------| + +** Installing ~pdf-tools~ elisp code +:PROPERTIES: +:CREATED: [2021-12-29 Wed 18:34] +:ID: 32c4fc3b-b4ea-43bd-b92c-bdf2d3831fcf +:END: +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 the code below somewhere in your ~.emacs~. Alternatively, and if you care about startup time, you may want to use the loader version instead. +#+begin_src elisp + (pdf-tools-install) ; Standard activation command + (pdf-loader-install) ; On demand loading, leads to faster startup time +#+end_src + +Once the Installation process is complete, check out [[id:19a3daea-6fa6-4ac3-9201-d2034c46ad8c][Easy Help for PDF Tools features]] and [[id:8dccd685-18b8-4c98-977a-0fe2d66b724c][Configuring PDF Tools features]] to get started! +** Updating ~pdf-tools~ +:PROPERTIES: +:CREATED: [2021-12-29 Wed 18:34] +:ID: 9dd62314-f5ad-4bd4-83fa-8e28343e3d9c +:END: +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. +* Features +:PROPERTIES: +:CREATED: [2021-12-29 Wed 18:34] +:ID: 555b4a2a-7881-49ac-a066-7e3f10034ca4 +:END: ++ View :: View PDF documents in a buffer with DocView-like bindings. [[id:18d362e1-a1a3-4c51-9d45-04e2c53d8c0c][More information here]]. ++ 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. [[id:5fff6471-a933-46d7-8ae9-b2fa4a9de952][More information here]]. ++ Attachments :: Save files attached to the PDF-file or list them in a dired buffer. ++ Outline :: Use ~imenu~ or a special buffer (~M-x pdf-outline~) 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. +** View and Navigate PDFs +:PROPERTIES: +:CREATED: [2021-12-30 Thu 18:25] +:ID: 18d362e1-a1a3-4c51-9d45-04e2c53d8c0c +:END: +PDFView Mode is an Emacs PDF viewer. It displays PDF files as PNG images in Emacs buffers. PDFs are navigable using DocView-like bindings. Once you have installed ~pdf-tools~, opening a PDF in Emacs will automatically trigger this mode. +*** Keybindings for navigating PDF documents +:PROPERTIES: +:CREATED: [2021-12-30 Thu 18:25] +:ID: 01864499-2286-4e64-91f5-f8133f53ec61 +:END: +| 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~ | +| First Page / Last 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 | ~l~ / ~r~ | +| Display Outline | ~o~ | +| Jump to Section from Outline | ~RETURN~ | +| Jump to Page | ~M-g g~ | +| Store position / Jump to position in register | ~m~ / ~'~ | +|-----------------------------------------------+-----------------------| +| | | +Note that ~pdf-tools~ renders the PDF as images inside Emacs. This means that all the keybindings of ~image-mode~ work on individual PDF pages as well. +| Image Mode | | +|------------------------+---------------------------------------------| +| image-scroll-right | ~C-x >~ / ~ ~ | +| image-scroll-left | ~C-x <~ / ~ ~ | +| image-scroll-up | ~C-v~ / ~ ~ | +| image-scroll-down | ~M-v~ / ~ ~ | +| image-forward-hscroll | ~C-f~ / ~right~ / ~ ~ | +| image-backward-hscroll | ~C-b~ / ~left~ / ~ ~ | +| image-bob | ~ ~ | +| image-eob | ~ ~ | +| image-bol | ~ ~ | +| image-eol | ~ ~ | +| image-scroll-down | ~ ~ | +| image-scroll-up | ~ ~ | +| image-scroll-left | ~ ~ | +| image-scroll-right | ~ ~ | +|------------------------+---------------------------------------------| +| | | + +*** Keybindings for manipulating display of PDF +:PROPERTIES: +:CREATED: [2021-12-30 Thu 18:33] +:ID: 73a18ea8-aa21-48d4-9d8b-dc64e3601000 +:END: +| 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 +:PROPERTIES: +:CREATED: [2021-12-30 Thu 16:58] +:ID: 5fff6471-a933-46d7-8ae9-b2fa4a9de952 +:END: +~pdf-tools~ supports working with PDF Annotations. You can display and list text and markup annotations (like squiggly, highlight), 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. +*** Keybindings for working with Annotations +:PROPERTIES: +:CREATED: [2021-12-30 Thu 17:08] +:ID: 243b3843-b912-430b-884a-641304755b92 +:END: +| 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~ | +| Enable/Disable Following Annotations | ~C-c C-f~ | +|--------------------------------------+---------------------------------------------------| +| Add and Edit Annotations | Select region via Mouse selection. | +| | Then left-click context menu OR keybindings below | +|--------------------------------------+---------------------------------------------------| +| Add a Markup Annotation | ~C-c C-a m~ | +| Add a Highlight Markup Annotation | ~C-c C-a h~ | +| Add a Strikeout Markup Annotation | ~C-c C-a o~ | +| Add a Squiggly Markup Annotation | ~C-c C-a s~ | +| Add an Underline Markup Annotation | ~C-c C-a u~ | +| Add a Text Annotation | ~C-c C-a t~ | +|--------------------------------------+---------------------------------------------------| +| | | + +** Working with AUCTeX +:PROPERTIES: +:CREATED: [2021-12-30 Thu 18:37] +:ID: 698bdbad-e5f1-4958-b61e-9ed12d4b1234 +:END: +*** Keybindings for working with AUCTeX +:PROPERTIES: +:CREATED: [2021-12-30 Thu 18:37] +:ID: ab7872c1-edd6-465d-9d1d-b621db6364a3 +:END: +| Syncing with AUCTeX | | +|-----------------------------------------------+-------------| +| Refresh File (e.g., after recompiling source) | ~g~ | +| Jump to PDF Location from Source | ~C-c C-g~ | +| Jump Source Location from PDF | ~C-mouse-1~ | + +** Miscellaneous features +:PROPERTIES: +:CREATED: [2021-12-30 Thu 18:37] +:ID: bbefb49d-fca8-4d4f-9d16-4a4ad1946d89 +:END: +*** Keybindings for miscellaneous features in PDF tools +:PROPERTIES: +:CREATED: [2021-12-30 Thu 18:35] +:ID: 9148deff-dd5a-46be-a48f-cd2f96b7ce19 +:END: +| Miscellaneous | | +|-----------------------------------------------+-----------| +| Print File | ~C-c C-p~ | + +** Easy Help for PDF Tools features +:PROPERTIES: +:CREATED: [2021-12-29 Wed 13:49] +:ID: 19a3daea-6fa6-4ac3-9201-d2034c46ad8c +:END: +#+begin_src elisp + M-x pdf-tools-help RET +#+end_src + +Run ~M-x pdf-tools-help~ inside Emacs, as shown above. It will list all the features provided by ~pdf-tools~ as well as the key-bindings for these features. + +** Configuring PDF Tools features +:PROPERTIES: +:CREATED: [2021-12-29 Wed 13:51] +:ID: 8dccd685-18b8-4c98-977a-0fe2d66b724c +:END: +Once you have read through the features provided by ~pdf-tools~, you probably want to customize the behavior of the features as per your requirements. Full customization of features is available by running the following: +#+begin_src elisp + M-x pdf-tools-customize RET +#+end_src + +* Known problems +:PROPERTIES: +:CREATED: [2021-12-29 Wed 18:29] +:ID: 4baf936a-2454-41c9-99db-177133ee9568 +:END: + +** linum-mode +:PROPERTIES: +:CREATED: [2021-12-29 Wed 18:34] +:ID: 73625d02-d472-4e7d-9805-db6d3b60e0ff +:END: +~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. + +** display-line-numbers-mode +:PROPERTIES: +:CREATED: [2022-01-03 Mon 08:31] +:ID: f178ba41-0f5a-4d22-b4a8-889af1af566e +:END: +This mode is an alternative to ~linum-mode~ and is available since Emacs 26. ~pdf-tools~ does not work well with it. For example, it makes horizontal navigation (such as ~C-f~, ~C-b~, ~C-x <~ or ~C-x >~ ) in a document impossible. + +** auto-revert +:PROPERTIES: +:CREATED: [2021-12-29 Wed 18:34] +:ID: 24b671c6-c242-4983-9d11-38421d2752e9 +:END: +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 +:PROPERTIES: +:CREATED: [2021-12-29 Wed 18:34] +:ID: 4766d18a-c02a-456d-8398-701bbea3ee80 +:END: +L/R scrolling breaks while zoomed into a pdf, with usage of sublimity smooth scrolling features + +* Key-bindings in PDF Tools +:PROPERTIES: +:CREATED: [2021-12-29 Wed 18:34] +:ID: fa99285a-437e-4be4-9a65-426db019019f +:END: +- [[brain-child:01864499-2286-4e64-91f5-f8133f53ec61][Keybindings for navigating PDF documents]] +- [[brain-child:243b3843-b912-430b-884a-641304755b92][Keybindings for working with Annotations]] +- [[brain-child:73a18ea8-aa21-48d4-9d8b-dc64e3601000][Keybindings for manipulating display of PDF]] +- [[brain-child:ab7872c1-edd6-465d-9d1d-b621db6364a3][Keybindings for working with AUCTeX]] +- [[brain-child:9148deff-dd5a-46be-a48f-cd2f96b7ce19][Keybindings for miscellaneous features in PDF tools]] + +* Tips and Tricks for Developers +:PROPERTIES: +:CREATED: [2021-12-29 Wed 18:34] +:ID: fd64c10c-4ea5-4ece-8d95-b723098dd4f6 +:END: +** Turn on debug mode +:PROPERTIES: +:CREATED: [2021-12-29 Wed 18:34] +:ID: 100fc888-7064-4dd3-9db4-c84a7e8f4af0 +:END: +#+begin_src elisp + M-x pdf-tools-toggle-debug RET +#+end_src +Toggling debug mode prints information about various operations in the ~*Messages*~ buffer, and this is useful to see what is happening behind the scenes + +** Run Emacs lisp tests locally +:PROPERTIES: +:CREATED: [2022-05-09 Mon 21:27] +:ID: 1CBE7325-A5A1-479B-9A98-BEEFBAC9D8FF +:END: +You can go to the ~pdf-tools~ folder and run ~make test~ to run the ERT tests and check if the changes you have made to the code break any of the tests. + +The tests are written in ERT, which is the built-in testing system in Emacs. However, they are run using ~Cask~ which you will have to install first, if you don't have it already. You can install ~Cask~ by following the instructions on their site at https://github.com/cask/cask +** Run server compilation tests locally +:PROPERTIES: +:CREATED: [2022-07-20 Wed 16:42] +:ID: 5327945D-9D92-4462-8172-7237DEF4C359 +:END: +You can go to the ~pdf-tools~ folder and run ~make server-test~ to check if the changes you have made to the server code break compilation on any of the supported operating systems. + +The tests build ~Podman~ images for all supported operating systems, so you will have to install ~Podman~ first, if you don't have already. You can install ~Podman~ by following the instructions on their site at https://podman.io/getting-started/installation + +Podman is compatible with Docker, so if you already have ~docker~ installed, you should be able to ~alias podman=docker~ on your shell and run the tests, without having to install Docker. (Note: I have not tested this) + +** Add a Dockerfile to automate server compilation testing +:PROPERTIES: +:CREATED: [2022-07-20 Wed 16:52] +:ID: A401543C-308B-4175-8212-5B78CD6C8389 +:END: +The ~server/test/docker~ folder contains Dockerfile templates used for testing that the ~epdfinfo~ server compiles correctly on various operating systems ([[id:5327945D-9D92-4462-8172-7237DEF4C359][more details here]]). + +To see the list of operating systems where compilation testing is supported, run ~make server-test-supported~. To see the list of operating systems where testing is unsupported, run ~make server-test-unsupported~. To add support, look into the ~server/test/docker/templates~ folder (~ubuntu~ files are a good example to refer to) + +* FAQs +:PROPERTIES: +:CREATED: [2021-12-30 Thu 22:04] +:ID: 3be6abe7-163e-4c3e-a7df-28e8470fe37f +:END: +** I'm on a Macbook and PDFs are rendering blurry +:PROPERTIES: +:CREATED: [2021-12-30 Thu 22:04] +:ID: 20ef86be-7c92-4cda-97ec-70a22484e689 +:END: +If you are on a Macbook with a Retina display, you may see PDFs as blurry due to the high resolution display. Use: + +#+begin_src elisp + (setq pdf-view-use-scaling t) +#+end_src + +to scale the images correctly when rendering them. + +** What Emacs versions does ~pdf-tools~ support? +:PROPERTIES: +:CREATED: [2022-01-02 Sun 10:12] +:ID: f44c66e6-402d-4154-b806-6bb4180a0a5b +:END: +~pdf-tools~ supports the 3 latest versions of Emacs major releases. At the moment of this writing, this means that the minimum supported Emacs version is ~26.1~. +** I want to add support for ~pdf-tools~ on =My Fav OS=. How do I do that? +:PROPERTIES: +:CREATED: [2022-04-25 Mon 21:50] +:ID: A34704B9-1B51-4614-8806-C4059F7B42D5 +:END: +I'm working on automating ~pdf-tools~ installation as much as possible, in order to improve the installation experience. If you want to add support for a new / currently unsupported Operating System, please modify the ~server/autobuild~ script. Say you want to support a new Operating System called MyFavOS. You need to do the following work: + +1. Add a call to ~os_myfavos~ under ~handle-options~ at the end of the existing call chain. Here we try and pick up the correct Operating System and install the relevant dependencies. +2. Add handling for the ~--os~ argument in ~os_argument~ for ~myfavos~, so that the appropriate function can be called to install pre-requisites. ~--os~ is the argument that we pass to the script from the command-line to indicate which OS we are on. +3. Create a ~os_myfavos~ function. This function checks if we are running on MyFavOS. If we are running on MyFavOS, it sets up ~PKGCMD~, ~PKGARGS~ and ~PACKAGES~ variables so that the appropriate package manager can install the dependencies as part of the rest of the ~autobuild~ script. +4. If you are adding support for your favorite operating system, consider adding automated testing support as well, to help me ensure that ~epdfinfo~ continues to compile correctly. See [[id:A401543C-308B-4175-8212-5B78CD6C8389][Add a Dockerfile to automate server compilation testing]] for more details. + +The idea here is to make the ~server/autobuild~ file the single place from which installation can happen on any Operating System. This makes building ~pdf-tools~ dead simple via the ~Makefile~. + +This seems like a lot of work, but it is not. If you need a reference, search for ~os_gentoo~ or ~os_debian~ in the ~server/autobuild~ file and see how these are setup and used. The functions are used to install dependencies on Gentoo and Debian respectively, and are simple to copy / change. + +When you make your changes, please be sure to test [[id:1CBE7325-A5A1-479B-9A98-BEEFBAC9D8FF][the elisp changes]] as well as [[id:5327945D-9D92-4462-8172-7237DEF4C359][the server code changes]] as described in the linked articles. + +** I am on a Macbook M1 and ~pdf-tools~ installation fails with a stack-trace +:PROPERTIES: +:CREATED: [2022-05-09 Mon 20:29] +:ID: 96D389D8-DD23-4FB0-996C-2D6F70A76BB2 +:END: +There have been a number of issues around ~pdf-tools~ installation problems on M1. ~M-x pdf-tools-install~ throws the following stack trace: +#+begin_example + 1 warning generated. + ld: warning: ignoring file /opt/homebrew/opt/gettext/lib/libintl.dylib, building for macOS-x86_64 but attempting to link with file built for macOS-arm64 + ld: warning: ignoring file /opt/homebrew/Cellar/glib/2.72.1/lib/libglib-2.0.dylib, building for macOS-x86_64 but attempting to link with file built for macOS-arm64 + ld: warning: ignoring file /opt/homebrew/Cellar/poppler/22.02.0/lib/libpoppler-glib.dylib, building for macOS-x86_64 but attempting to link with file built for macOS-arm64 + ld: warning: ignoring file /opt/homebrew/Cellar/glib/2.72.1/lib/libgobject-2.0.dylib, building for macOS-x86_64 but attempting to link with file built for macOS-arm64 + ld: warning: ignoring file /opt/homebrew/Cellar/poppler/22.02.0/lib/libpoppler.dylib, building for macOS-x86_64 but attempting to link with file built for macOS-arm64 + ld: warning: ignoring file /opt/homebrew/Cellar/cairo/1.16.0_5/lib/libcairo.dylib, building for macOS-x86_64 but attempting to link with file built for macOS-arm64 + ld: warning: ignoring file /opt/homebrew/Cellar/libpng/1.6.37/lib/libpng16.dylib, building for macOS-x86_64 but attempting to link with file built for macOS-arm64 + ld: warning: ignoring file /opt/homebrew/Cellar/zlib/1.2.11/lib/libz.dylib, building for macOS-x86_64 but attempting to link with file built for macOS-arm64 + Undefined symbols for architecture x86_64: +#+end_example + +This happens because M1 architecture is =ARM64=, whereas the Emacs App you are using has been compiled for the =x86_64= architecture. The way to solve this problem is to install a version of Emacs which has been compiled for the M1. As of today, [2022-05-09 Mon], the latest version of Emacs available on https://emacsformacosx.com/ is natively compiled and you will not face these issues on it. Please remove your current Emacs App and install it from https://emacsformacosx.com/. + +Thank you. + +PS: How do I know if the Emacs I'm running has been compiled correctly? + +You can see this by opening the =Activity Monitor=, selecting =Emacs=, clicking on the =Info= key, and then clicking on =Sample=. The =Code Type= field in the Sample output will show you how your Application has been compiled. Here is the output for EmacsForMacOSX (you can see that it's =ARM64=): +#+begin_example + Sampling process 61824 for 3 seconds with 1 millisecond of run time between samples + Sampling completed, processing symbols... + Analysis of sampling Emacs-arm64-11 (pid 61824) every 1 millisecond + Process: Emacs-arm64-11 [61824] + Path: /Applications/Emacs.app/Contents/MacOS/Emacs-arm64-11 + Load Address: 0x1007f0000 + Identifier: org.gnu.Emacs + Version: Version 28.1 (9.0) + Code Type: ARM64 + Platform: macOS +#+end_example + +If your Emacs is compiled for x86, the =Code Type= will be =x86_64=. + +** I am a developer, making changes to the ~pdf-tools~ source code +:PROPERTIES: +:CREATED: [2022-05-09 Mon 21:31] +:ID: 2D173424-C211-4474-B0D0-83F4381CAFFA +:END: +Thank you for taking the time to contribute back to the code. You may find some useful notes in the [[id:fd64c10c-4ea5-4ece-8d95-b723098dd4f6][Tips and Tricks for Developers]] section. Please be sure to check it out! diff --git a/org/elpa/pdf-tools-20220823.513/build/Makefile b/org/elpa/pdf-tools-20220823.513/build/Makefile new file mode 100644 index 0000000..f776884 --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/build/Makefile @@ -0,0 +1,103 @@ +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 + +server-test-supported: server/test/Makefile + $(MAKE) -C server/test print + +server-test-unsupported: server/test/Makefile + $(MAKE) -C server/test print-failing diff --git a/org/elpa/pdf-tools-20220823.513/build/server/.gitignore b/org/elpa/pdf-tools-20220823.513/build/server/.gitignore new file mode 100644 index 0000000..a3cc612 --- /dev/null +++ b/org/elpa/pdf-tools-20220823.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/org/elpa/pdf-tools-20220823.513/build/server/Makefile.am b/org/elpa/pdf-tools-20220823.513/build/server/Makefile.am new file mode 100644 index 0000000..b39e805 --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/build/server/Makefile.am @@ -0,0 +1,44 @@ +bin_PROGRAMS = epdfinfo +epdfinfo_CFLAGS = -Wall $(glib_CFLAGS) $(poppler_glib_CFLAGS) $(poppler_CFLAGS) \ + $(png_CFLAGS) +# Need -lm to link libm.so.6 +epdfinfo_LDADD = $(glib_LIBS) $(poppler_glib_LIBS) $(poppler_LIBS) \ + $(png_LIBS) libsynctex.a $(zlib_LIBS) -lm +epdfinfo_SOURCES = epdfinfo.c epdfinfo.h + +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/org/elpa/pdf-tools-20220823.513/build/server/autobuild b/org/elpa/pdf-tools-20220823.513/build/server/autobuild new file mode 100644 index 0000000..57d596b --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/build/server/autobuild @@ -0,0 +1,585 @@ +#!/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 <()$\`"'\'' ]/\\&/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 + [ $? -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 + 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.69 automake%1.15 poppler poppler-utils png" + export AUTOCONF_VERSION=2.69 + export AUTOMAKE_VERSION=1.15 + return 0 +} + +# Fedora +os_fedora() { + if ! [ -e "/etc/fedora-release" ]; then + return 1 + fi + PKGCMD=dnf + PKGARGS=install + PACKAGES="autoconf + automake + gcc + 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 + gcc + libpng-dev + libpoppler-dev + libpoppler-glib-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 + autoconf + automake + mingw-w64-x86_64-libpng + mingw-w64-x86_64-poppler + mingw-w64-x86_64-imagemagick + mingw-w64-x86_64-toolchain + mingw-w64-x86_64-openssl + mingw-w64-x86_64-zlib" ;; + MINGW32) + PACKAGES="base-devel + autoconf + automake + mingw-w64-i686-libpng + mingw-w64-i686-poppler + mingw-w64-i686-imagemagick + mingw-w64-i686-toolchain + mingw-w64-i686-openssl + 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 autoconf automake" + PKG_INSTALL_AS_ROOT= + # brew installs libffi as keg-only, meaning we need to set + # PKG_CONFIG_PATH manually so configure can find it + export PKG_CONFIG_PATH="${PKG_CONFIG_PATH}:$(brew --prefix libffi)/lib/pkgconfig/" + elif which port >/dev/null 2>&1; then + PKGCMD=port + PKGARGS=install + PACKAGES="pkgconfig poppler autoconf 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 + if ! nix-instantiate --eval -E "" &>/dev/null; then + echo "File 'nixpkgs' was not found in the Nix path. Using NixOS/nixpkgs" + NIX_PATH="nixpkgs=https://github.com/nixos/nixpkgs/archive/master.tar.gz:$NIX_PATH" + fi + command="AUTOBUILD_NIX_SHELL=true" + command="$command;export AUTOBUILD_NIX_SHELL" + command="$command;$(quote "$0" "$@")" + exec nix-shell --pure --run "$command" \ + -p 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/pkgconf + media-libs/libpng + sys-devel/autoconf + sys-devel/automake + sys-devel/gcc + sys-devel/make + sys-libs/glibc + sys-libs/zlib" + return 0 +} + +# Void +os_void() { + if [ -f "/etc/os-release" ]; then + . /etc/os-release + if [ "$NAME" != "void" ]; then + return 1 + fi + else + return 1 + fi + PACKAGES="autoconf + automake + libpng-devel + poppler-devel + poppler-glib-devel + zlib-devel + make + pkgconf" + PKGCMD=xbps-install + PKGARGS="-Sy" + return 0 +} + +# openSUSE (TODO: add support for micro versions) +os_opensuse() { + if [ -f "/etc/os-release" ]; then + . /etc/os-release + if [ "$ID" != "opensuse-leap" ] && [ "$ID" != "opensuse-tumbleweed" ]; then + return 1 + fi + else + return 1 + fi + PACKAGES="make + automake + autoconf + gcc + libpng16-devel + libpng16-compat-devel + zlib-devel + libpoppler-devel + libpoppler-glib-devel + glib2-devel + pkgconf" + PKGCMD=zypper + PKGARGS="install" + 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 "$@";; + void) os_void "$@";; + opensuse) os_opensuse "$@";; + *) 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_void "$@" || \ +os_opensuse "$@" || \ +{ + 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/org/elpa/pdf-tools-20220823.513/build/server/autogen.sh b/org/elpa/pdf-tools-20220823.513/build/server/autogen.sh new file mode 100644 index 0000000..0e0824c --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/build/server/autogen.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +echo "Running autoreconf..." + +autoreconf -i diff --git a/org/elpa/pdf-tools-20220823.513/build/server/configure.ac b/org/elpa/pdf-tools-20220823.513/build/server/configure.ac new file mode 100644 index 0000000..9a8c46d --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/build/server/configure.ac @@ -0,0 +1,96 @@ +# -*- 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_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 + +# 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 (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/org/elpa/pdf-tools-20220823.513/build/server/epdfinfo.c b/org/elpa/pdf-tools-20220823.513/build/server/epdfinfo.c new file mode 100644 index 0000000..770035d --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/build/server/epdfinfo.c @@ -0,0 +1,3721 @@ +/* Copyright (C) 2013, 2014 Andreas Politz + * + * Author: 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 . */ + +#include + +#include +#ifdef HAVE_ERR_H +# include +#endif +#ifdef HAVE_ERROR_H +# include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 its 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_IRUSR | S_IWUSR); + 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 Its 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 Its 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 Its 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; + GDate *date; + + 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 */ + date = poppler_annot_markup_get_date (ma); + if (date != NULL && g_date_valid(date)) + { + gchar datebuf[128]; + g_date_strftime (datebuf, 127, "%x", date); + print_response_string (datebuf, NONE); + g_date_free (date); + } + + /* <<< 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); + + poppler_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/org/elpa/pdf-tools-20220823.513/build/server/epdfinfo.h b/org/elpa/pdf-tools-20220823.513/build/server/epdfinfo.h new file mode 100644 index 0000000..542b577 --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/build/server/epdfinfo.h @@ -0,0 +1,246 @@ +// Copyright (C) 2013, 2014 Andreas Politz + +// Author: 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 . + +#ifndef _EPDF_H_ +#define _EPDF_H_ _EPDF_H_ +#include "config.h" +#include +#include +#include + +/* 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; +#endif /* _EPDF_H_ */ diff --git a/org/elpa/pdf-tools-20220823.513/build/server/poppler-versions b/org/elpa/pdf-tools-20220823.513/build/server/poppler-versions new file mode 100644 index 0000000..9d1aea9 --- /dev/null +++ b/org/elpa/pdf-tools-20220823.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/org/elpa/pdf-tools-20220823.513/build/server/synctex_parser.c b/org/elpa/pdf-tools-20220823.513/build/server/synctex_parser.c new file mode 100644 index 0000000..27be608 --- /dev/null +++ b/org/elpa/pdf-tools-20220823.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 +#include +#include +#include +#include +#include + +#if defined(HAVE_LOCALE_H) +#include +#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 +# 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,"ed,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.statussynctex = 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. + * statushandle: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 0already_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_CURreader->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=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.statusptr) { + 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.statusptr) { + 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.statusreader->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 (end0) { + 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))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 (statusversion = is.integer; + /* Read all the input records */ + do { + status = __synctex_parse_new_input(scanner).status; + if (statusoutput_fmt = ss.string; + if ((status=_synctex_scan_named(scanner,"Magnification:"))pre_magnification = is.integer; + if ((status=_synctex_scan_named(scanner,"Unit:"))pre_unit = is.integer; + if ((status=_synctex_scan_named(scanner,"X Offset:"))pre_x_offset = is.integer; + if ((status=_synctex_scan_named(scanner,"Y Offset:"))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_OK) { + fs.value *= 72.27f*65536; + } else if (fs.status= SYNCTEX_STATUS_OK) { + fs.value *= 72.27f*65536/2.54f; + } else if (fs.status= SYNCTEX_STATUS_OK) { + fs.value *= 72.27f*65536/25.4f; + } else if (fs.status= SYNCTEX_STATUS_OK) { + fs.value *= 65536.0f; + } else if (fs.status= SYNCTEX_STATUS_OK) { + fs.value *= 72.27f/72*65536.0f; + } else if (fs.status= SYNCTEX_STATUS_OK) { + fs.value *= 12.0*65536.0f; + } else if (fs.status= SYNCTEX_STATUS_OK) { + fs.value *= 1.0f; + } else if (fs.status= SYNCTEX_STATUS_OK) { + fs.value *= 1238.0f/1157*65536.0f; + } else if (fs.status= SYNCTEX_STATUS_OK) { + fs.value *= 14856.0f/1157*65536; + } else if (fs.status= SYNCTEX_STATUS_OK) { + fs.value *= 685.0f/642*65536; + } else if (fs.status= SYNCTEX_STATUS_OK) { + fs.value *= 1370.0f/107*65536; + } else if (fs.statusunit = 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 (statusx_offset = fs.value; + goto next_line; + } else if (statusx_offset = fs.value; + goto next_line; + } else if (statusflags.postamble && (status=_synctex_match_string(scanner,"Postamble:"))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.hmax) { + _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.hmax) { + _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.vmax) { + _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 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.hmax) { + _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.vmax) { + _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)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)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)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_CURflags.postamble = 1; + SYNCTEX_RETURN(status); + } + status = _synctex_next_line(scanner); + if (status" 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_CURreader->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)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) 0) { +# ifdef SYNCTEX_NOTHING +# pragma mark + SCAN MROF +# endif + ++SYNCTEX_CUR; + --form_depth; + if (_synctex_next_line(scanner)(line %i)\n",SYNCTEX_CUR, scanner->reader->line_number+1); + if (_synctex_next_line(scanner)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;iclass_->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;iclass_->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;iclass_->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.status500 + 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.statusref_in_sheet); + if (ns.statusref_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;inumber_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;inumber_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.status500 + 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 (statusreader = _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 (status500 + 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(0input: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)*/ { + __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->hh; /* 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->hh; /* 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->hh; /* 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<<<hh + 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->vv; /* 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->vv; /* 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->vv; /* 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->vv; /* 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 (areaother_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_heightother_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->vmin.v) { + /* Regions 1, 2 or 3 */ + if (hit->hmin.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->hmin.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->hmin.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
*/ + 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 +#include +#include + +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;nsheet; + 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/org/elpa/pdf-tools-20220823.513/build/server/synctex_parser.h b/org/elpa/pdf-tools-20220823.513/build/server/synctex_parser.h new file mode 100644 index 0000000..9e0a00e --- /dev/null +++ b/org/elpa/pdf-tools-20220823.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/org/elpa/pdf-tools-20220823.513/build/server/synctex_parser_advanced.h b/org/elpa/pdf-tools-20220823.513/build/server/synctex_parser_advanced.h new file mode 100644 index 0000000..95c2ac1 --- /dev/null +++ b/org/elpa/pdf-tools-20220823.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/org/elpa/pdf-tools-20220823.513/build/server/synctex_parser_local.h b/org/elpa/pdf-tools-20220823.513/build/server/synctex_parser_local.h new file mode 100644 index 0000000..b53439f --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/build/server/synctex_parser_local.h @@ -0,0 +1,3 @@ +#include +#define printf(fmt, args...) (fprintf (stderr, (fmt), ## args)) +#define SYNCTEX_INLINE diff --git a/org/elpa/pdf-tools-20220823.513/build/server/synctex_parser_readme.txt b/org/elpa/pdf-tools-20220823.513/build/server/synctex_parser_readme.txt new file mode 100644 index 0000000..4a25b58 --- /dev/null +++ b/org/elpa/pdf-tools-20220823.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/org/elpa/pdf-tools-20220823.513/build/server/synctex_parser_utils.c b/org/elpa/pdf-tools-20220823.513/build/server/synctex_parser_utils.c new file mode 100644 index 0000000..5be3d09 --- /dev/null +++ b/org/elpa/pdf-tools-20220823.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 +#include +#include +#include +#include + +#include +#include +#include + +#include + +#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 +#include /* 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 +#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)+20) { + 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/org/elpa/pdf-tools-20220823.513/build/server/synctex_parser_utils.h b/org/elpa/pdf-tools-20220823.513/build/server/synctex_parser_utils.h new file mode 100644 index 0000000..4dd4e17 --- /dev/null +++ b/org/elpa/pdf-tools-20220823.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 + +#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/org/elpa/pdf-tools-20220823.513/build/server/synctex_parser_version.txt b/org/elpa/pdf-tools-20220823.513/build/server/synctex_parser_version.txt new file mode 100644 index 0000000..d2ab029 --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/build/server/synctex_parser_version.txt @@ -0,0 +1 @@ +1.21 diff --git a/org/elpa/pdf-tools-20220823.513/build/server/synctex_version.h b/org/elpa/pdf-tools-20220823.513/build/server/synctex_version.h new file mode 100644 index 0000000..5665ccb --- /dev/null +++ b/org/elpa/pdf-tools-20220823.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/org/elpa/pdf-tools-20220823.513/build/server/test/docker/.gitignore b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/.gitignore new file mode 100644 index 0000000..a6d9461 --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/.gitignore @@ -0,0 +1,2 @@ +*.Dockerfile +*.build diff --git a/org/elpa/pdf-tools-20220823.513/build/server/test/docker/lib/run-tests b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/lib/run-tests new file mode 100644 index 0000000..43fe5e5 --- /dev/null +++ b/org/elpa/pdf-tools-20220823.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/org/elpa/pdf-tools-20220823.513/build/server/test/docker/lib/yes-or-enter b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/lib/yes-or-enter new file mode 100644 index 0000000..b19cc5a --- /dev/null +++ b/org/elpa/pdf-tools-20220823.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/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/Dockerfile.common.in b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/Dockerfile.common.in new file mode 100644 index 0000000..083b19a --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/Dockerfile.common.in @@ -0,0 +1,4 @@ +ADD . /epdfinfo +WORKDIR /epdfinfo +RUN make -s distclean || true +CMD ["sh", "./test/docker/lib/run-tests"] diff --git a/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/arch.Dockerfile.in.FAILING b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/arch.Dockerfile.in.FAILING new file mode 100644 index 0000000..a85762b --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/arch.Dockerfile.in.FAILING @@ -0,0 +1,4 @@ +# -*- dockerfile -*- +FROM archlinux:latest +RUN pacman -Syu --noconfirm --noprogressbar +# @TODO: The official Archlinux image does not seem to have any form of shell. Marking this as FAILING. diff --git a/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/centos.Dockerfile.in.FAILING b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/centos.Dockerfile.in.FAILING new file mode 100644 index 0000000..1da2ab8 --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/centos.Dockerfile.in.FAILING @@ -0,0 +1,6 @@ +# -*- dockerfile -*- +FROM centos:centos7 +RUN sed -i -e "s|mirrorlist=|#mirrorlist=|g" /etc/yum.repos.d/CentOS-* +RUN sed -i -e "s|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g" /etc/yum.repos.d/CentOS-* +RUN yum update -y +# @TODO: Since CentOS is no more, do I even want to try and fix this? diff --git a/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/debian-10.Dockerfile.in b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/debian-10.Dockerfile.in new file mode 100644 index 0000000..a2ad6bb --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/debian-10.Dockerfile.in @@ -0,0 +1,6 @@ +# -*- dockerfile -*- +# Debian 10 is known as buster +FROM debian:10 +ARG DEBIAN_FRONTEND=noninteractive +# Need to install make, tzdata here to avoid stupid prompts when running package install via autobuild +RUN apt-get update -y && apt-get install -y make tzdata diff --git a/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/debian-11.Dockerfile.in b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/debian-11.Dockerfile.in new file mode 100644 index 0000000..dae6b6c --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/debian-11.Dockerfile.in @@ -0,0 +1,6 @@ +# -*- dockerfile -*- +# Debian 11 is known as bullseye +FROM debian:11 +ARG DEBIAN_FRONTEND=noninteractive +# Need to install make, tzdata here to avoid stupid prompts when running package install via autobuild +RUN apt-get update -y && apt-get install -y make tzdata diff --git a/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/debian-9.Dockerfile.in b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/debian-9.Dockerfile.in new file mode 100644 index 0000000..b558ade --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/debian-9.Dockerfile.in @@ -0,0 +1,6 @@ +# -*- dockerfile -*- +# Debian 9 is known as stretch +FROM debian:9 +ARG DEBIAN_FRONTEND=noninteractive +# Need to install make, tzdata here to avoid stupid prompts when running package install via autobuild +RUN apt-get update -y && apt-get install -y make tzdata diff --git a/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/fedora-34.Dockerfile.in b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/fedora-34.Dockerfile.in new file mode 100644 index 0000000..dbb736e --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/fedora-34.Dockerfile.in @@ -0,0 +1,4 @@ +# -*- dockerfile -*- +FROM fedora:34 +# Need to install make, tzdata here to avoid stupid prompts when running package install via autobuild +RUN dnf update -y && dnf install -y make tzdata diff --git a/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/fedora-35.Dockerfile.in b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/fedora-35.Dockerfile.in new file mode 100644 index 0000000..915cac4 --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/fedora-35.Dockerfile.in @@ -0,0 +1,4 @@ +# -*- dockerfile -*- +FROM fedora:35 +# Need to install make, tzdata here to avoid stupid prompts when running package install via autobuild +RUN dnf update -y && dnf install -y make tzdata diff --git a/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/fedora-36.Dockerfile.in b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/fedora-36.Dockerfile.in new file mode 100644 index 0000000..c36a716 --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/fedora-36.Dockerfile.in @@ -0,0 +1,4 @@ +# -*- dockerfile -*- +FROM fedora:36 +# Need to install make, tzdata here to avoid stupid prompts when running package install via autobuild +RUN dnf update -y && dnf install -y make tzdata diff --git a/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/freebsd.Dockerfile.in.FAILING b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/freebsd.Dockerfile.in.FAILING new file mode 100644 index 0000000..e69de29 diff --git a/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/gentoo.Dockerfile.in.FAILING b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/gentoo.Dockerfile.in.FAILING new file mode 100644 index 0000000..7e1d520 --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/gentoo.Dockerfile.in.FAILING @@ -0,0 +1,6 @@ +# -*- dockerfile -*- +FROM gentoo/stage3 +RUN emerge --sync +# IF you see this error, just ignore it: !!! It seems /run is not mounted. Process management may malfunction. +# Note that gentoo takes a **long** time to build and run, that's okay. +# @TODO: Currently, running this errors out because it cannot find glib-2.0, needs fixing. diff --git a/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/macos.Dockerfile.in.FAILING b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/macos.Dockerfile.in.FAILING new file mode 100644 index 0000000..e69de29 diff --git a/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/msys2.Dockerfile.in.FAILING b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/msys2.Dockerfile.in.FAILING new file mode 100644 index 0000000..e69de29 diff --git a/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/nixos.Dockerfile.in.FAILING b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/nixos.Dockerfile.in.FAILING new file mode 100644 index 0000000..e69de29 diff --git a/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/openbsd.Dockerfile.in.FAILING b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/openbsd.Dockerfile.in.FAILING new file mode 100644 index 0000000..e69de29 diff --git a/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/opensuse.Dockerfile.in.FAILING b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/opensuse.Dockerfile.in.FAILING new file mode 100644 index 0000000..e69de29 diff --git a/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/ubuntu-18.Dockerfile.in b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/ubuntu-18.Dockerfile.in new file mode 100644 index 0000000..b139c84 --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/ubuntu-18.Dockerfile.in @@ -0,0 +1,5 @@ +# -*- dockerfile -*- +FROM ubuntu:bionic +ARG DEBIAN_FRONTEND=noninteractive +# Need to install make, tzdata here to avoid stupid prompts when running package install via autobuild +RUN apt-get update --fix-missing -y && apt-get install -y make tzdata diff --git a/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/ubuntu-20.Dockerfile.in b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/ubuntu-20.Dockerfile.in new file mode 100644 index 0000000..f650856 --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/ubuntu-20.Dockerfile.in @@ -0,0 +1,5 @@ +# -*- dockerfile -*- +FROM ubuntu:focal +ARG DEBIAN_FRONTEND=noninteractive +# Need to install make, tzdata here to avoid stupid prompts when running package install via autobuild +RUN apt-get update --fix-missing -y && apt-get install -y make tzdata diff --git a/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/ubuntu-22.Dockerfile.in b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/ubuntu-22.Dockerfile.in new file mode 100644 index 0000000..217daed --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/ubuntu-22.Dockerfile.in @@ -0,0 +1,5 @@ +# -*- dockerfile -*- +FROM ubuntu:jammy +ARG DEBIAN_FRONTEND=noninteractive +# Need to install make, tzdata here to avoid stupid prompts when running package install via autobuild +RUN apt-get update -y && apt-get install -y make tzdata diff --git a/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/void.Dockerfile.in.FAILING b/org/elpa/pdf-tools-20220823.513/build/server/test/docker/templates/void.Dockerfile.in.FAILING new file mode 100644 index 0000000..e69de29 diff --git a/org/elpa/pdf-tools-20220823.513/pdf-annot.el b/org/elpa/pdf-tools-20220823.513/pdf-annot.el new file mode 100644 index 0000000..81cb639 --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/pdf-annot.el @@ -0,0 +1,1880 @@ +;;; pdf-annot.el --- Annotation support for PDF files. -*- lexical-binding: t -*- + +;; Copyright (C) 2013, 2014 Andreas Politz + +;; Author: Andreas Politz +;; 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 . + +;;; 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 +;; * ================================================================== * + +;;; Code: + +(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-activate-handler' will be +called. + +This hook is meant to allow for custom annotations. FIXME: +Implement and describe basic org example." + :type 'hook) + +(defcustom pdf-annot-default-text-annotation-properties nil + "Alist of initial properties for new text annotations." + :type '(alist :key-type symbol :value-type sexp)) + +(defcustom pdf-annot-default-markup-annotation-properties nil + "Alist of initial properties for new markup annotations." + :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\"." + + :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." + :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." + :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}\"." + :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." + :type 'boolean) + +(defcustom pdf-annot-activate-created-annotations nil + "Whether to activate (i.e. edit) created annotations." + :type 'boolean) + +(defcustom pdf-annot-attachment-display-buffer-action nil + "The display action used when displaying attachments." + :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." + :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 + "Controls the behavior of `pdf-annot-modified-functions'. + +If non-nil, `pdf-annot-modified-functions' are not run on any +annotation change.") + +(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." + :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 "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) + "Show the user a color menu for their annotation 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) + "Show the user an icon menu for the annotation 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) + "Return the annotation object for annotation ID. + +Optionally take the BUFFER name of the PDF buffer. When none is +provided, the `current-buffer' is picked up." + (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) + "Return non-nil if annotation A is of type text." + (eq 'text (pdf-annot-get a 'type))) + +(defun pdf-annot-markup-annotation-p (a) + "Return non-nil if annotation A is a known markup type. + +Annotation types are defined in `pdf-annot-markup-annotation-types'." + (not (null + (memq (pdf-annot-get a 'type) + pdf-annot-markup-annotation-types)))) + +(defun pdf-annot-property-modifiable-p (a property) + "Return non-nil if PROPERTY for annotation A is editable." + (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) + "Run handler functions on A to activate the annotation. + +Activation functions are defined in `pdf-annot-activate-handler-functions'." + (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) + "The default activation function to run on annotation A. + +Activation functions are defined in `pdf-annot-activate-handler-functions'." + (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 its 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. + +SIZE is a cons (SX . SY), by which edges are scaled." + (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) + "Create a local keymap for interacting with ANNOTATION using the mouse. + +ID is the identifier for the ANNOTATION, as returned +`pdf-annot-get-id'. MOVEABLE-P indicates whether the annotation +is moveable." + ;; 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) + "Create and add 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) + "Add a text annotation using the mouse. + +EV describes the captured mouse event." + (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. + +LIST-OF-EDGES defines the annotation boundary. COLOR defines the +annotation color and PROPERTY-ALIST defines additional annotation +properties. 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. + +LIST-OF-EDGES defines the annotation boundary. COLOR defines the +annotation color and PROPERTY-ALIST defines additional annotation +properties. 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. + +LIST-OF-EDGES defines the annotation boundary. COLOR defines the +annotation color and PROPERTY-ALIST defines additional annotation +properties. 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. + +LIST-OF-EDGES defines the annotation boundary. COLOR defines the +annotation color and PROPERTY-ALIST defines additional annotation +properties. 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)) + ;; pdf-tools can only work with png files, so this + ;; binding ensures that pdf-tools can print the + ;; latex-preview regardless of the user + ;; configuration. + (org-preview-latex-default-process 'dvipng) + ;; For backward compatibility with emacs-version < 26.1 + (org-latex-create-formula-image-program 'dvipng) + (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 + 'org-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 `org-mode'." + :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." + :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) + +(defun pdf-annot-edit-contents-finalize (do-save &optional do-kill) + "Finalize edit-operations on an Annotation. + +If DO-SAVE is t, save the changes to annotation content without +asking. If DO-SAVE is 'ask, check if the user if contents should +be saved. + +If DO-KILL is t, kill all windows displaying the annotation +contents. Else just bury the buffers." + (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 () + "Internal function to save the contents of the annotation under editing." + (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 () + "Save the change made to the current annotation." + (interactive) + (pdf-annot-edit-contents-finalize t)) + +(defun pdf-annot-edit-contents-abort () + "Abort the change made to the current annotation." + (interactive) + (pdf-annot-edit-contents-finalize nil t)) + +(defun pdf-annot-edit-contents-noselect (a) + "Internal function to setup all prerequisites for editing annotation A. + +At any given point of time, only one annotation can be in edit mode." + (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) + "Edit the contents of annotation 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) + "Edit the contents of the annotation described by mouse event 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." + :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")))) + +(defcustom pdf-annot-list-highlight-type t + "Whether to highlight \"Type\" column annotation list with annotation color." + :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 () + "Return all the annotations of this PDF buffer as a `tabulated-list'." + (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) + "Return a formatter function for annotation A. + +A formatter function takes a format cons-cell and returns +pretty-printed output." + (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 (propertize (pdf-annot-print-property a 'modified) + 'date + (pdf-annot-get 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" + ;; @TODO: Remove the hard-coded index values here, and figure out a + ;; way to properly link this to the index values of + ;; `pdf-annot-list-format'. + + ;; @TODO: Add tests for annotation formatting and display + (let* ((page-sorter + (lambda (a b) + (< (string-to-number (aref (cadr a) 0)) + (string-to-number (aref (cadr b) 0))))) + (date-sorter + (lambda (a b) + (time-less-p (get-text-property 0 'date (aref (cadr a) 3)) + (get-text-property 0 'date (aref (cadr b) 3))))) + (format-generator + (lambda (format) + (let ((field (car format)) + (width (cdr format))) + (cl-case field + (page `("Pg." + ,width + ,page-sorter + :read-only t + :right-align t)) + (date `("Date" + ,width + ,date-sorter + :read-only 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) + "List all the annotations in the current buffer. + +Goto the annotation A in the list." + (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) + "Update the list of annotations on any change. + +This is an internal function which runs as a hook in various situations." + (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) + "Show the contents of an Annotation. + +For an annotation identified by ID, belonging to PDF in BUFFER, +get the contents and display them on demand." + (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) + "Define bulk operations in Annotation list buffer. + +OP is the operation that the user wants to execute. Supported +operations are `delete' and `find-entry'. + +ARGS contain the annotation-ids to operate on." + (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) + "Display the Annotation ID in the PDF file. + +This allows us to follow the tabulated-list of annotations and +have the PDF buffer automatically move along with us." + (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 + "Make the PDF follow the annotations in the list buffer." + :group 'pdf-annot + (unless (derived-mode-p 'pdf-annot-list-mode) + (error "Not 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/org/elpa/pdf-tools-20220823.513/pdf-cache.el b/org/elpa/pdf-tools-20220823.513/pdf-cache.el new file mode 100644 index 0000000..31073ff --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/pdf-cache.el @@ -0,0 +1,499 @@ +;;; pdf-cache.el --- Cache time-critical or frequent epdfinfo queries. -*- lexical-binding:t -*- + +;; Copyright (C) 2013 Andreas Politz + +;; Author: Andreas Politz +;; 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 . + +;;; Commentary: +;; +;;; Code: +;; + +(require 'pdf-macs) +(require 'pdf-info) +(require 'pdf-util) + + +;; * ================================================================== * +;; * Customiazations +;; * ================================================================== * + +(defcustom pdf-cache-image-limit 64 + "Maximum number of cached PNG images per buffer." + :type 'integer + :group 'pdf-cache + :group 'pdf-view) + +(defcustom pdf-cache-prefetch-delay 0.5 + "Idle time in seconds before prefetching images starts." + :group 'pdf-view + :type 'number) + +(defcustom pdf-cache-prefetch-pages-function + 'pdf-cache-prefetch-pages-function-default + "A function returning a list of pages to be prefetched. + +It is called with no arguments in the PDF window and should +return a list of page-numbers, determining the pages that should +be prefetched and their order." + :group 'pdf-view + :type 'function) + + +;; * ================================================================== * +;; * Simple Value cache +;; * ================================================================== * + +(defvar-local pdf-cache--data nil) + +(defvar pdf-annot-modified-functions) + +(defun pdf-cache--initialize () + "Initialize the cache to store document data. + +Note: The cache is only initialized once. After that it needs to +be cleared before this function makes any changes to it. This is +an internal function and not meant to be directly used." + (unless pdf-cache--data + (setq pdf-cache--data (make-hash-table)) + (add-hook 'pdf-info-close-document-hook #'pdf-cache-clear-data nil t) + (add-hook 'pdf-annot-modified-functions + #'pdf-cache--clear-data-of-annotations + nil t))) + +(defun pdf-cache--clear-data-of-annotations (fn) + "Clear the data cache when annotations are modified. + +FN is a closure as described in `pdf-annot-modified-functions'. + +Note: This is an internal function and not meant to be directly used." + (apply #'pdf-cache-clear-data-of-pages + (mapcar (lambda (a) + (cdr (assq 'page a))) + (funcall fn t)))) + +(defun pdf-cache--data-put (key value &optional page) + "Put KEY with VALUE in the cache of PAGE, return value." + (pdf-cache--initialize) + (puthash page (cons (cons key value) + (assq-delete-all + key + (gethash page pdf-cache--data))) + pdf-cache--data) + value) + +(defun pdf-cache--data-get (key &optional page) + "Get value of KEY in the cache of PAGE. + +Returns a cons \(HIT . VALUE\), where HIT is non-nil if KEY was +stored previously for PAGE and VALUE its value. Otherwise HIT +is nil and VALUE undefined." + (pdf-cache--initialize) + (let ((elt (assq key (gethash page pdf-cache--data)))) + (if elt + (cons t (cdr elt)) + (cons nil nil)))) + +(defun pdf-cache--data-clear (key &optional page) + "Remove KEY from the cache of PAGE." + (pdf-cache--initialize) + (puthash page + (assq-delete-all key (gethash page pdf-cache--data)) + pdf-cache--data) + nil) + +(defun pdf-cache-clear-data-of-pages (&rest pages) + "Remove all PAGES from the cache." + (when pdf-cache--data + (dolist (page pages) + (remhash page pdf-cache--data)))) + +(defun pdf-cache-clear-data () + "Remove the entire cache." + (interactive) + (when pdf-cache--data + (clrhash pdf-cache--data))) + +(defmacro define-pdf-cache-function (command &optional page-arg-p) + "Define a simple data cache function. + +COMMAND is the name of the command, e.g. number-of-pages. It +should have a corresponding pdf-info function. If PAGE-ARG-P is +non-nil, define a one-dimensional cache indexed by the page +number. Otherwise the value is constant for each document, like +e.g. number-of-pages. + +Both args are unevaluated." + + (let ((args (if page-arg-p (list 'page))) + (fn (intern (format "pdf-cache-%s" command))) + (ifn (intern (format "pdf-info-%s" command))) + (doc (format "Cached version of `pdf-info-%s', which see. + +Make sure, not to modify its return value." command))) + `(defun ,fn ,args + ,doc + (let ((hit-value (pdf-cache--data-get ',command ,(if page-arg-p 'page)))) + (if (car hit-value) + (cdr hit-value) + (pdf-cache--data-put + ',command + ,(if page-arg-p + (list ifn 'page) + (list ifn)) + ,(if page-arg-p 'page))))))) + +(define-pdf-cache-function pagelinks t) +(define-pdf-cache-function number-of-pages) +;; The boundingbox may change if annotations change. +(define-pdf-cache-function boundingbox t) +(define-pdf-cache-function textregions t) +(define-pdf-cache-function pagesize t) + + +;; * ================================================================== * +;; * PNG image LRU cache +;; * ================================================================== * + +(defvar pdf-cache-image-inihibit nil + "Non-nil, if the image cache should be bypassed.") + +(defvar-local pdf-cache--image-cache nil) + +(defmacro pdf-cache--make-image (page width data hash) + "Make the image that we store in the image cache. + +An image is a tuple of PAGE WIDTH DATA HASH." + `(list ,page ,width ,data ,hash)) +(defmacro pdf-cache--image/page (img) + "Return the page value for IMG." + `(nth 0 ,img)) +(defmacro pdf-cache--image/width (img) + "Return the width value for IMG." + `(nth 1 ,img)) +(defmacro pdf-cache--image/data (img) + "Return the data value for IMG." + `(nth 2 ,img)) +(defmacro pdf-cache--image/hash (img) + "Return the hash value for IMG." + `(nth 3 ,img)) + +(defun pdf-cache--image-match (image page min-width &optional max-width hash) + "Match IMAGE with specs. + +IMAGE should be a list as created by `pdf-cache--make-image'. + +Return non-nil, if IMAGE's page is the same as PAGE, its width +is at least MIN-WIDTH and at most MAX-WIDTH and its stored +hash-value is `eql' to HASH." + (and (= (pdf-cache--image/page image) + page) + (or (null min-width) + (>= (pdf-cache--image/width image) + min-width)) + (or (null max-width) + (<= (pdf-cache--image/width image) + max-width)) + (eql (pdf-cache--image/hash image) + hash))) + +(defun pdf-cache-lookup-image (page min-width &optional max-width hash) + "Return PAGE's cached PNG data as a string or nil. + +Return an image of at least MIN-WIDTH and, if non-nil, maximum +width MAX-WIDTH and `eql' HASH value. + +Does not modify the cache. See also `pdf-cache-get-image'." + (let ((image (car (cl-member + (list page min-width max-width hash) + pdf-cache--image-cache + :test (lambda (spec image) + (apply #'pdf-cache--image-match image spec)))))) + (and image + (pdf-cache--image/data image)))) + +(defun pdf-cache-get-image (page min-width &optional max-width hash) + "Return PAGE's PNG data as a string. + +Return an image of at least MIN-WIDTH and, if non-nil, maximum +width MAX-WIDTH and `eql' HASH value. + +Remember that image was recently used. + +Returns nil, if no matching image was found." + (let ((cache pdf-cache--image-cache) + image) + ;; Find it in the cache. + (while (and (setq image (pop cache)) + (not (pdf-cache--image-match + image page min-width max-width hash)))) + ;; Remove it and push it to the front. + (when image + (setq pdf-cache--image-cache + (cons image (delq image pdf-cache--image-cache))) + (pdf-cache--image/data image)))) + +(defun pdf-cache-put-image (page width data &optional hash) + "Cache image of PAGE with WIDTH, DATA and HASH. + +DATA should the string of a PNG image of width WIDTH and from +page PAGE in the current buffer. See `pdf-cache-get-image' for +the HASH argument. + +This function always returns nil." + (unless pdf-cache--image-cache + (add-hook 'pdf-info-close-document-hook #'pdf-cache-clear-images nil t) + (add-hook 'pdf-annot-modified-functions + #'pdf-cache--clear-images-of-annotations nil t)) + (push (pdf-cache--make-image page width data hash) + pdf-cache--image-cache) + ;; Forget old image(s). + (when (> (length pdf-cache--image-cache) + pdf-cache-image-limit) + (if (> pdf-cache-image-limit 1) + (setcdr (nthcdr (1- pdf-cache-image-limit) + pdf-cache--image-cache) + nil) + (setq pdf-cache--image-cache nil))) + nil) + +(defun pdf-cache-clear-images () + "Clear the image cache." + (setq pdf-cache--image-cache nil)) + +(defun pdf-cache-clear-images-if (fn) + "Remove images from the cache according to FN. + +FN should be function accepting 4 Arguments \(PAGE WIDTH DATA +HASH\). It should return non-nil, if the image should be removed +from the cache." + (setq pdf-cache--image-cache + (cl-remove-if + (lambda (image) + (funcall + fn + (pdf-cache--image/page image) + (pdf-cache--image/width image) + (pdf-cache--image/data image) + (pdf-cache--image/hash image))) + pdf-cache--image-cache))) + + +(defun pdf-cache--clear-images-of-annotations (fn) + "Clear the images cache when annotations are modified. + +FN is a closure as described in `pdf-annot-modified-functions'. + +Note: This is an internal function and not meant to be directly used." + (apply #'pdf-cache-clear-images-of-pages + (mapcar (lambda (a) + (cdr (assq 'page a))) + (funcall fn t)))) + +(defun pdf-cache-clear-images-of-pages (&rest pages) + "Remove all images of PAGES from the image cache." + (pdf-cache-clear-images-if + (lambda (page &rest _) (memq page pages)))) + +(defun pdf-cache-renderpage (page min-width &optional max-width) + "Render PAGE according to MIN-WIDTH and MAX-WIDTH. + +Return the PNG data of an image as a string, such that its width +is at least MIN-WIDTH and, if non-nil, at most MAX-WIDTH. + +If such an image is not available in the cache, call +`pdf-info-renderpage' to create one." + (if pdf-cache-image-inihibit + (pdf-info-renderpage page min-width) + (or (pdf-cache-get-image page min-width max-width) + (let ((data (pdf-info-renderpage page min-width))) + (pdf-cache-put-image page min-width data) + data)))) + +(defun pdf-cache-renderpage-text-regions (page width single-line-p + &rest selection) + "Render PAGE according to WIDTH, SINGLE-LINE-P and SELECTION. + +See also `pdf-info-renderpage-text-regions' and +`pdf-cache-renderpage'." + (if pdf-cache-image-inihibit + (apply #'pdf-info-renderpage-text-regions + page width single-line-p nil selection) + (let ((hash (sxhash + (format "%S" (cons 'renderpage-text-regions + (cons single-line-p selection)))))) + (or (pdf-cache-get-image page width width hash) + (let ((data (apply #'pdf-info-renderpage-text-regions + page width single-line-p nil selection))) + (pdf-cache-put-image page width data hash) + data))))) + +(defun pdf-cache-renderpage-highlight (page width &rest regions) + "Highlight PAGE according to WIDTH and REGIONS. + +See also `pdf-info-renderpage-highlight' and +`pdf-cache-renderpage'." + (if pdf-cache-image-inihibit + (apply #'pdf-info-renderpage-highlight + page width nil regions) + (let ((hash (sxhash + (format "%S" (cons 'renderpage-highlight + regions))))) + (or (pdf-cache-get-image page width width hash) + (let ((data (apply #'pdf-info-renderpage-highlight + page width nil regions))) + (pdf-cache-put-image page width data hash) + data))))) + + +;; * ================================================================== * +;; * Prefetching images +;; * ================================================================== * + +(defvar-local pdf-cache--prefetch-pages nil + "Pages to be prefetched.") + +(defvar-local pdf-cache--prefetch-timer nil + "Timer used when prefetching images.") + +(define-minor-mode pdf-cache-prefetch-minor-mode + "Try to load images which will probably be needed in a while." + :group 'pdf-cache + (pdf-cache--prefetch-cancel) + (cond + (pdf-cache-prefetch-minor-mode + (pdf-util-assert-pdf-buffer) + (add-hook 'pre-command-hook #'pdf-cache--prefetch-stop nil t) + ;; FIXME: Disable the time when the buffer is killed or its + ;; major-mode changes. + (setq pdf-cache--prefetch-timer + (run-with-idle-timer (or pdf-cache-prefetch-delay 1) t + #'pdf-cache--prefetch-start (current-buffer)))) + (t + (remove-hook 'pre-command-hook #'pdf-cache--prefetch-stop t)))) + +(defun pdf-cache-prefetch-pages-function-default () + "The default function to prefetch pages. + +See `pdf-cache-prefetch-pages-function' for an explanation of +what this function does." + (let ((page (pdf-view-current-page))) + (pdf-util-remove-duplicates + (cl-remove-if-not + (lambda (page) + (and (>= page 1) + (<= page (pdf-cache-number-of-pages)))) + (append + ;; +1, -1, +2, -2, ... + (let ((sign 1) + (incr 1)) + (mapcar (lambda (_) + (setq page (+ page (* sign incr)) + sign (- sign) + incr (1+ incr)) + page) + (number-sequence 1 16))) + ;; First and last + (list 1 (pdf-cache-number-of-pages)) + ;; Links + (mapcar + (apply-partially 'alist-get 'page) + (cl-remove-if-not + (lambda (link) (eq (alist-get 'type link) 'goto-dest)) + (pdf-cache-pagelinks + (pdf-view-current-page))))))))) + +(defvar pdf-view-use-scaling) +(defun pdf-cache--prefetch-pages (window image-width) + "Internal function to prefetch pages and store them in the cache. + +WINDOW and IMAGE-WIDTH decide the page and scale of the final image." + (when (and (eq window (selected-window)) + (pdf-util-pdf-buffer-p)) + (let ((page (pop pdf-cache--prefetch-pages))) + (while (and page + (pdf-cache-lookup-image + page + image-width + (if (not pdf-view-use-scaling) + image-width + (* 2 image-width)))) + (setq page (pop pdf-cache--prefetch-pages))) + (pdf-util-debug + (when (null page) + (message "Prefetching done."))) + (when page + (let* ((buffer (current-buffer)) + (pdf-info-asynchronous + (lambda (status data) + (when (and (null status) + (eq window + (selected-window)) + (eq buffer (window-buffer))) + (with-current-buffer (window-buffer) + (when (derived-mode-p 'pdf-view-mode) + (pdf-cache-put-image + page image-width data) + (image-size (pdf-view-create-page page)) + (pdf-util-debug + (message "Prefetched page %s." page)) + ;; Avoid max-lisp-eval-depth + (run-with-timer + 0.001 nil + #'pdf-cache--prefetch-pages window image-width))))))) + (condition-case err + (pdf-info-renderpage page image-width) + (error + (pdf-cache-prefetch-minor-mode -1) + (signal (car err) (cdr err))))))))) + +(defvar pdf-cache--prefetch-started-p nil + "Guard against multiple prefetch starts. + +Used solely in `pdf-cache--prefetch-start'.") + +(defun pdf-cache--prefetch-start (buffer) + "Start prefetching images in BUFFER." + (when (and pdf-cache-prefetch-minor-mode + (not pdf-cache--prefetch-started-p) + (pdf-util-pdf-buffer-p) + (not isearch-mode) + (null pdf-cache--prefetch-pages) + (eq (window-buffer) buffer) + (fboundp pdf-cache-prefetch-pages-function)) + (let* ((pdf-cache--prefetch-started-p t) + (pages (funcall pdf-cache-prefetch-pages-function))) + (setq pdf-cache--prefetch-pages + (butlast pages (max 0 (- (length pages) + pdf-cache-image-limit)))) + (pdf-cache--prefetch-pages + (selected-window) + (car (pdf-view-desired-image-size)))))) + +(defun pdf-cache--prefetch-stop () + "Stop prefetching images in current buffer." + (setq pdf-cache--prefetch-pages nil)) + +(defun pdf-cache--prefetch-cancel () + "Cancel prefetching images in current buffer." + (pdf-cache--prefetch-stop) + (when pdf-cache--prefetch-timer + (cancel-timer pdf-cache--prefetch-timer)) + (setq pdf-cache--prefetch-timer nil)) + +(provide 'pdf-cache) +;;; pdf-cache.el ends here diff --git a/org/elpa/pdf-tools-20220823.513/pdf-dev.el b/org/elpa/pdf-tools-20220823.513/pdf-dev.el new file mode 100644 index 0000000..6ea5d8c --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/pdf-dev.el @@ -0,0 +1,89 @@ +;;; pdf-dev.el --- Mother's little development helper -*- lexical-binding: t; -*- + +;; Copyright (C) 2015 Andreas Politz + +;; Author: Andreas Politz +;; 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 . + +;;; 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) + "Restart the epdfinfo server. + +BUFFER is the PDF buffer and STATUS is the compilation status of +building epdfinfo." + (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/org/elpa/pdf-tools-20220823.513/pdf-history.el b/org/elpa/pdf-tools-20220823.513/pdf-history.el new file mode 100644 index 0000000..e380cf5 --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/pdf-history.el @@ -0,0 +1,172 @@ +;;; pdf-history.el --- A simple stack-based history in PDF buffers. -*- lexical-binding: t -*- + +;; Copyright (C) 2013, 2014 Andreas Politz + +;; Author: Andreas Politz +;; 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 . + +;;; 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) + (define-key kmap (kbd "l") #'pdf-history-backward) + (define-key kmap (kbd "r") #'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/org/elpa/pdf-tools-20220823.513/pdf-info.el b/org/elpa/pdf-tools-20220823.513/pdf-info.el new file mode 100644 index 0000000..408dd3f --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/pdf-info.el @@ -0,0 +1,1745 @@ +;;; pdf-info.el --- Extract info from pdf-files via a helper process. -*- lexical-binding: t -*- + +;; Copyright (C) 2013, 2014 Andreas Politz + +;; Author: Andreas Politz +;; 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 . + +;;; 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." + :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." + :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*\"." + :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." + :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." + :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." + :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<>endobj 2 0" + " obj<>endobj 3 0 obj<>endobj\nxref\n0 4\n00000000" + "0065535 f\n0000000010 00000 n\n0000000053 00000 n\n00000" + "00102 00000 n\ntrailer<>\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) + +(when (< emacs-major-version 27) (advice-add 'tq-process-buffer :around #'pdf-info--tq-workaround)) +(defun pdf-info--tq-workaround (orig-fun tq &rest args) + "Fix a bug in trunk where the wrong callback gets called. + +ORIG-FUN is the callback that should be called. TQ and ARGS are +the transmission-queue and arguments to the callback." + ;; FIXME: Make me iterative. + (if (not (equal (car (process-command (tq-process tq))) + pdf-info-epdfinfo-program)) + (apply orig-fun tq args) + (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) + `(,var (cdr (assq ',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 (cond + ((not file-or-buffer) (current-buffer)) + ((bufferp file-or-buffer) file-or-buffer) + ((stringp file-or-buffer) + (find-buffer-visiting file-or-buffer))))) + (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/org/elpa/pdf-tools-20220823.513/pdf-isearch.el b/org/elpa/pdf-tools-20220823.513/pdf-isearch.el new file mode 100644 index 0000000..4d87a2e --- /dev/null +++ b/org/elpa/pdf-tools-20220823.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 +;; 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 . + +;;; 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 + "This mode is enabled when isearch is active in a PDF file." + :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/org/elpa/pdf-tools-20220823.513/pdf-links.el b/org/elpa/pdf-tools-20220823.513/pdf-links.el new file mode 100644 index 0000000..d54981e --- /dev/null +++ b/org/elpa/pdf-tools-20220823.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 +;; 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 . + +;;; 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.\\ + +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/org/elpa/pdf-tools-20220823.513/pdf-loader.el b/org/elpa/pdf-tools-20220823.513/pdf-loader.el new file mode 100644 index 0000000..b1eab34 --- /dev/null +++ b/org/elpa/pdf-tools-20220823.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 +;; 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 . + +;;; 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/org/elpa/pdf-tools-20220823.513/pdf-macs.el b/org/elpa/pdf-tools-20220823.513/pdf-macs.el new file mode 100644 index 0000000..e4ac6ea --- /dev/null +++ b/org/elpa/pdf-tools-20220823.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 +;; 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 . + +;;; 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/org/elpa/pdf-tools-20220823.513/pdf-misc.el b/org/elpa/pdf-tools-20220823.513/pdf-misc.el new file mode 100644 index 0000000..b6bb896 --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/pdf-misc.el @@ -0,0 +1,300 @@ +;;; pdf-misc.el --- Miscellaneous commands for PDF buffer. -*- lexical-binding: t; -*- + +;; Copyright (C) 2013, 2014 Andreas Politz + +;; Author: Andreas Politz +;; 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 . + +;;; Commentary: +;; + + +(require 'pdf-view) +(require 'pdf-util) +(require 'imenu) + + + +;;; Code: + +(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." + (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))) + (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) + "Print the PDF doc FILENAME. + +`pdf-misc-print-program' handles the print program, which see for +definition of INTERACTIVE-P." + (interactive + (list (pdf-view-buffer-file-name) t)) + (cl-check-type filename (and string (satisfies file-readable-p))) + (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/org/elpa/pdf-tools-20220823.513/pdf-occur.el b/org/elpa/pdf-tools-20220823.513/pdf-occur.el new file mode 100644 index 0000000..47bf2d2 --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/pdf-occur.el @@ -0,0 +1,820 @@ +;;; pdf-occur.el --- Display matching lines of PDF documents. -*- lexical-binding: t -*- + +;; Copyright (C) 2013, 2014 Andreas Politz + +;; Author: Andreas Politz +;; 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 . + +;;; 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.") + +(defface pdf-occur-page-face + '((default (:inherit font-lock-type-face))) + "Face used to highlight page numbers in the list buffer.") + +(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." + :type 'integer) + +(defcustom pdf-occur-prefer-string-search nil + "If non-nil, reverse the meaning of the regexp-p prefix-arg." + :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 its 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`. \\ + +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 + (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'.") + +(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'.") + +(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 its 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)))) + +(advice-add 'tabulated-list-init-header :after #'pdf-occur--update-header) +(defun pdf-occur--update-header (&rest _) + "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/org/elpa/pdf-tools-20220823.513/pdf-outline.el b/org/elpa/pdf-tools-20220823.513/pdf-outline.el new file mode 100644 index 0000000..a9212b7 --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/pdf-outline.el @@ -0,0 +1,599 @@ +;;; pdf-outline.el --- Outline for PDF buffer -*- lexical-binding: t -*- + +;; Copyright (C) 2013, 2014 Andreas Politz + +;; Author: Andreas Politz +;; 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 . + +;;; 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) + +(defcustom pdf-outline-enable-imenu t + "Whether `imenu' should be enabled in PDF documents." + :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." + :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." + :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." + :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 its displayed page number." + :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." + :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 its 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 its 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/org/elpa/pdf-tools-20220823.513/pdf-sync.el b/org/elpa/pdf-tools-20220823.513/pdf-sync.el new file mode 100644 index 0000000..c5deebf --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/pdf-sync.el @@ -0,0 +1,768 @@ +;;; pdf-sync.el --- Use synctex to correlate LaTeX-Sources with PDF positions. -*- lexical-binding:t -*- +;; Copyright (C) 2013, 2014 Andreas Politz + +;; Author: Andreas Politz +;; 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 . + +;;; 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 its whitespace, deletes certain notorious +;; characters and translates certain other characters 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 its PDF file. + +This key is added to `TeX-source-correlate-method', when +command `pdf-sync-minor-mode' is activated and this map is defined." + :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." + :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." + :type 'hook) + +(defcustom pdf-sync-forward-display-action nil + "Display action used when displaying PDF buffers." + :type 'display-buffer--action-custom-type) + +(defcustom pdf-sync-backward-display-action nil + "Display action used when displaying TeX buffers." + :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." + :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." + :type '(repeat function)) + + +;;;###autoload +(define-minor-mode pdf-sync-minor-mode + "Correlate a PDF position with the TeX file. +\\ +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." + :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 its LaTeX equivalent names allows +the heuristic backward search to find its 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." + :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 + (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))))))) + +(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/org/elpa/pdf-tools-20220823.513/pdf-tools-autoloads.el b/org/elpa/pdf-tools-20220823.513/pdf-tools-autoloads.el new file mode 100644 index 0000000..75cc55d --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/pdf-tools-autoloads.el @@ -0,0 +1,637 @@ +;;; pdf-tools-autoloads.el --- automatically extracted autoloads -*- lexical-binding: t -*- +;; +;;; 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. + +This is a minor mode. If called interactively, toggle the +`Pdf-Annot minor mode' mode. If the prefix argument is positive, +enable the mode, and if it is zero or negative, disable the mode. + +If called from Lisp, toggle the mode if ARG is `toggle'. Enable +the mode if ARG is nil, omitted, or is a positive number. +Disable the mode if ARG is a negative number. + +To check whether the minor mode is enabled in the current buffer, +evaluate `pdf-annot-minor-mode'. + +The mode's hook is called both when the mode is enabled and when +it is disabled. + +\\{pdf-annot-minor-mode-map} + +\(fn &optional ARG)" t nil) + +(register-definition-prefixes "pdf-annot" '("pdf-annot-")) + +;;;*** + +;;;### (autoloads nil "pdf-cache" "pdf-cache.el" (0 0 0 0)) +;;; Generated autoloads from pdf-cache.el + +(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 + +(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. + +This is a minor mode. If called interactively, toggle the +`Pdf-History minor mode' mode. If the prefix argument is +positive, enable the mode, and if it is zero or negative, disable +the mode. + +If called from Lisp, toggle the mode if ARG is `toggle'. Enable +the mode if ARG is nil, omitted, or is a positive number. +Disable the mode if ARG is a negative number. + +To check whether the minor mode is enabled in the current buffer, +evaluate `pdf-history-minor-mode'. + +The mode's hook is called both when the mode is enabled and when +it is disabled. + +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) + +(register-definition-prefixes "pdf-history" '("pdf-history-")) + +;;;*** + +;;;### (autoloads nil "pdf-info" "pdf-info.el" (0 0 0 0)) +;;; Generated autoloads from pdf-info.el + +(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. + +This is a minor mode. If called interactively, toggle the +`Pdf-Isearch minor mode' mode. If the prefix argument is +positive, enable the mode, and if it is zero or negative, disable +the mode. + +If called from Lisp, toggle the mode if ARG is `toggle'. Enable +the mode if ARG is nil, omitted, or is a positive number. +Disable the mode if ARG is a negative number. + +To check whether the minor mode is enabled in the current buffer, +evaluate `pdf-isearch-minor-mode'. + +The mode's hook is called both when the mode is enabled and when +it is disabled. + +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) + +(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.\\ + +This is a minor mode. If called interactively, toggle the +`Pdf-Links minor mode' mode. If the prefix argument is positive, +enable the mode, and if it is zero or negative, disable the mode. + +If called from Lisp, toggle the mode if ARG is `toggle'. Enable +the mode if ARG is nil, omitted, or is a positive number. +Disable the mode if ARG is a negative number. + +To check whether the minor mode is enabled in the current buffer, +evaluate `pdf-links-minor-mode'. + +The mode's hook is called both when the mode is enabled and when +it is disabled. + +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) + +(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) + +(register-definition-prefixes "pdf-loader" '("pdf-loader--")) + +;;;*** + +;;;### (autoloads nil "pdf-macs" "pdf-macs.el" (0 0 0 0)) +;;; Generated autoloads from pdf-macs.el + +(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. + +This is a minor mode. If called interactively, toggle the +`Pdf-Misc minor mode' mode. If the prefix argument is positive, +enable the mode, and if it is zero or negative, disable the mode. + +If called from Lisp, toggle the mode if ARG is `toggle'. Enable +the mode if ARG is nil, omitted, or is a positive number. +Disable the mode if ARG is a negative number. + +To check whether the minor mode is enabled in the current buffer, +evaluate `pdf-misc-minor-mode'. + +The mode's hook is called both when the mode is enabled and when +it is disabled. + +\(fn &optional ARG)" t nil) + +(autoload 'pdf-misc-size-indication-minor-mode "pdf-misc" "\ +Provide a working size indication in the mode-line. + +This is a minor mode. If called interactively, toggle the +`Pdf-Misc-Size-Indication minor mode' mode. If the prefix +argument is positive, enable the mode, and if it is zero or +negative, disable the mode. + +If called from Lisp, toggle the mode if ARG is `toggle'. Enable +the mode if ARG is nil, omitted, or is a positive number. +Disable the mode if ARG is a negative number. + +To check whether the minor mode is enabled in the current buffer, +evaluate `pdf-misc-size-indication-minor-mode'. + +The mode's hook is called both when the mode is enabled and when +it is disabled. + +\(fn &optional ARG)" t nil) + +(autoload 'pdf-misc-menu-bar-minor-mode "pdf-misc" "\ +Display a PDF Tools menu in the menu-bar. + +This is a minor mode. If called interactively, toggle the +`Pdf-Misc-Menu-Bar minor mode' mode. If the prefix argument is +positive, enable the mode, and if it is zero or negative, disable +the mode. + +If called from Lisp, toggle the mode if ARG is `toggle'. Enable +the mode if ARG is nil, omitted, or is a positive number. +Disable the mode if ARG is a negative number. + +To check whether the minor mode is enabled in the current buffer, +evaluate `pdf-misc-menu-bar-minor-mode'. + +The mode's hook is called both when the mode is enabled and when +it is disabled. + +\(fn &optional ARG)" t nil) + +(autoload 'pdf-misc-context-menu-minor-mode "pdf-misc" "\ +Provide a right-click context menu in PDF buffers. + +This is a minor mode. If called interactively, toggle the +`Pdf-Misc-Context-Menu minor mode' mode. If the prefix argument +is positive, enable the mode, and if it is zero or negative, +disable the mode. + +If called from Lisp, toggle the mode if ARG is `toggle'. Enable +the mode if ARG is nil, omitted, or is a positive number. +Disable the mode if ARG is a negative number. + +To check whether the minor mode is enabled in the current buffer, +evaluate `pdf-misc-context-menu-minor-mode'. + +The mode's hook is called both when the mode is enabled and when +it is disabled. + +\\{pdf-misc-context-menu-minor-mode-map} + +\(fn &optional ARG)" t nil) + +(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. + +This is a minor mode. If called interactively, toggle the +`Pdf-Occur-Global minor mode' mode. If the prefix argument is +positive, enable the mode, and if it is zero or negative, disable +the mode. + +If called from Lisp, toggle the mode if ARG is `toggle'. Enable +the mode if ARG is nil, omitted, or is a positive number. +Disable the mode if ARG is a negative number. + +To check whether the minor mode is enabled in the current buffer, +evaluate `(default-value \\='pdf-occur-global-minor-mode)'. + +The mode's hook is called both when the mode is enabled and when +it is disabled. + +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. + +This is a minor mode. If called interactively, toggle the +`Pdf-Occur-Ibuffer minor mode' mode. If the prefix argument is +positive, enable the mode, and if it is zero or negative, disable +the mode. + +If called from Lisp, toggle the mode if ARG is `toggle'. Enable +the mode if ARG is nil, omitted, or is a positive number. +Disable the mode if ARG is a negative number. + +To check whether the minor mode is enabled in the current buffer, +evaluate `pdf-occur-ibuffer-minor-mode'. + +The mode's hook is called both when the mode is enabled and when +it is disabled. + +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. + +This is a minor mode. If called interactively, toggle the +`Pdf-Occur-Dired minor mode' mode. If the prefix argument is +positive, enable the mode, and if it is zero or negative, disable +the mode. + +If called from Lisp, toggle the mode if ARG is `toggle'. Enable +the mode if ARG is nil, omitted, or is a positive number. +Disable the mode if ARG is a negative number. + +To check whether the minor mode is enabled in the current buffer, +evaluate `pdf-occur-dired-minor-mode'. + +The mode's hook is called both when the mode is enabled and when +it is disabled. + +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) + +(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. + +This is a minor mode. If called interactively, toggle the +`Pdf-Outline minor mode' mode. If the prefix argument is +positive, enable the mode, and if it is zero or negative, disable +the mode. + +If called from Lisp, toggle the mode if ARG is `toggle'. Enable +the mode if ARG is nil, omitted, or is a positive number. +Disable the mode if ARG is a negative number. + +To check whether the minor mode is enabled in the current buffer, +evaluate `pdf-outline-minor-mode'. + +The mode's hook is called both when the mode is enabled and when +it is disabled. + +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) + +(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. +\\ +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'. + +This is a minor mode. If called interactively, toggle the +`Pdf-Sync minor mode' mode. If the prefix argument is positive, +enable the mode, and if it is zero or negative, disable the mode. + +If called from Lisp, toggle the mode if ARG is `toggle'. Enable +the mode if ARG is nil, omitted, or is a positive number. +Disable the mode if ARG is a negative number. + +To check whether the minor mode is enabled in the current buffer, +evaluate `pdf-sync-minor-mode'. + +The mode's hook is called both when the mode is enabled and when +it is disabled. + +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) + +(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" "\ +Show a Help buffer for `pdf-tools'." t nil) + +(register-definition-prefixes "pdf-tools" '("pdf-tools-")) + +;;;*** + +;;;### (autoloads nil "pdf-util" "pdf-util.el" (0 0 0 0)) +;;; Generated autoloads from pdf-util.el + +(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) + +(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. + +This is a minor mode. If called interactively, toggle the +`Pdf-Virtual-Global minor mode' mode. If the prefix argument is +positive, enable the mode, and if it is zero or negative, disable +the mode. + +If called from Lisp, toggle the mode if ARG is `toggle'. Enable +the mode if ARG is nil, omitted, or is a positive number. +Disable the mode if ARG is a negative number. + +To check whether the minor mode is enabled in the current buffer, +evaluate `(default-value \\='pdf-virtual-global-minor-mode)'. + +The mode's hook is called both when the mode is enabled and when +it is disabled. + +\(fn &optional ARG)" t nil) + +(autoload 'pdf-virtual-buffer-create "pdf-virtual" "\ + + +\(fn &optional FILENAMES BUFFER-NAME DISPLAY-P)" t nil) + +(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/org/elpa/pdf-tools-20220823.513/pdf-tools-pkg.el b/org/elpa/pdf-tools-20220823.513/pdf-tools-pkg.el new file mode 100644 index 0000000..eb5f356 --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/pdf-tools-pkg.el @@ -0,0 +1,15 @@ +(define-package "pdf-tools" "20220823.513" "Support library for PDF documents" + '((emacs "24.3") + (nadvice "0.3") + (tablist "1.0") + (let-alist "1.0.4")) + :commit "1a0a30c54dc3effdba4781a2983115d4b6993260" :authors + '(("Andreas Politz" . "mail@andreas-politz.de")) + :maintainer + '("Vedang Manerikar" . "vedang.manerikar@gmail.com") + :keywords + '("files" "multimedia") + :url "http://github.com/vedang/pdf-tools/") +;; Local Variables: +;; no-byte-compile: t +;; End: diff --git a/org/elpa/pdf-tools-20220823.513/pdf-tools.el b/org/elpa/pdf-tools-20220823.513/pdf-tools.el new file mode 100644 index 0000000..9d15269 --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/pdf-tools.el @@ -0,0 +1,544 @@ +;;; pdf-tools.el --- Support library for PDF documents -*- lexical-binding:t -*- + +;; Copyright (C) 2013, 2014 Andreas Politz + +;; Author: Andreas Politz +;; Maintainer: Vedang Manerikar +;; URL: http://github.com/vedang/pdf-tools/ +;; Keywords: files, multimedia +;; Package: pdf-tools +;; Version: 1.0.0snapshot +;; Package-Requires: ((emacs "24.3") (nadvice "0.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 . + +;;; 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) + (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 (satisfies file-directory-p)) + (setq target-directory (file-name-as-directory + (expand-file-name target-directory))) + (cl-check-type build-directory (and (not null) + (satisfies file-directory-p))) + (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)) + ;; This when check should not be necessary, but somehow dead + ;; buffers are showing up here. See + ;; https://github.com/vedang/pdf-tools/pull/93 + (when (buffer-live-p buf) + (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) + "Check if the current buffer is a PDF document. + +Optionally, take BUFFER as an argument and check if it is 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) + "Throw an error if the current BUFFER does not contain a PDF document." + (unless (pdf-tools-pdf-buffer-p buffer) + (error "Buffer does not contain a PDF document"))) + +(defun pdf-tools-set-modes-enabled (enable &optional modes) + "Enable/Disable all the pdf-tools modes on the current buffer based on ENABLE. + +Accepts MODES as a optional argument to enable/disable specific 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 () + "Show a Help buffer for `pdf-tools'." + (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 () + "Turn debugging on/off for pdf-tools." + (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/org/elpa/pdf-tools-20220823.513/pdf-util.el b/org/elpa/pdf-tools-20220823.513/pdf-util.el new file mode 100644 index 0000000..09788a2 --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/pdf-util.el @@ -0,0 +1,1364 @@ +;;; pdf-util.el --- PDF Utility functions. -*- lexical-binding: t -*- + +;; Copyright (C) 2013, 2014 Andreas Politz + +;; Author: Andreas Politz +;; 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 . + +;;; 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))))) + +(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)))) + +(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)))))) + +;; * ================================================================== * +;; * 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))) + +;; FIXME: Defined in `pdf-view' but we can't require it here because it +;; requires us :-( +(defvar pdf-view-midnight-colors) + +(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)) + +(advice-add 'enable-theme :after #'pdf-util--clear-faces-cache) +(defun pdf-util--clear-faces-cache (&rest _) + (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) + `(apply #'vector + (cl-loop for i from 1 to ,rows + collect (make-vector ,columns nil)))) + (mset (matrix row column newelt) + `(aset (aref ,matrix ,row) ,column ,newelt)) + (mref (matrix row column) + `(aref (aref ,matrix ,row) ,column))) + (let* ((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) 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 (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/org/elpa/pdf-tools-20220823.513/pdf-view.el b/org/elpa/pdf-tools-20220823.513/pdf-view.el new file mode 100644 index 0000000..0820e8f --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/pdf-view.el @@ -0,0 +1,1727 @@ +;;; pdf-view.el --- View PDF documents. -*- lexical-binding:t -*- + +;; Copyright (C) 2013 Andreas Politz + +;; Author: Andreas Politz +;; 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 . + +;;; 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") +(declare-function pdf-tools-pdf-buffer-p "pdf-tools") + +;; * ================================================================== * +;; * 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 "") 'forward-page) + (define-key map (kbd "") '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 "") '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 "") '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) + ;; 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)) + + ;; Disable pixel-scroll-precision-mode locally if enabled + (if (bound-and-true-p pixel-scroll-precision-mode) + (set (make-local-variable 'pixel-scroll-precision-mode) nil)) + + ;; 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") + (advice-add 'cua-copy-region + :before-until + #'cua-copy-region--pdf-view-advice) + (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))) + +(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--after-revert () + "Reload the local copy in case of a remote file, and close the document." + (when pdf-view--buffer-file-name + (write-region nil nil pdf-view--buffer-file-name nil 'no-message)) + (pdf-info-close)) + +(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-view--after-revert + 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 +;; * ================================================================== * + +(defvar pdf-view-inhibit-redisplay nil) +(defvar pdf-view-inhibit-hotspots nil) + +(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)) + (win-scroll (window-vscroll nil pdf-view-have-image-mode-pixel-vscroll)) + (img-scroll (image-scroll-up arg))) + (when (or + ;; There is no next line for the image to scroll to + (and img-scroll (= win-scroll img-scroll)) + ;; 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)) + (win-scroll (window-vscroll nil pdf-view-have-image-mode-pixel-vscroll)) + (img-scroll (image-scroll-down arg))) + (when (or + ;; There is no previous line for the image to scroll to + (and img-scroll (= win-scroll img-scroll)) + ;; 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)))) + +(defun pdf-view-center-in-window () + "Center PDF in window horizontally." + (interactive) + (image-set-window-hscroll + (/ (* (- (car (pdf-view-image-size)) + (window-pixel-width)) + (window-width)) + 2 (window-pixel-width))) ; convert from pixel to character width + (pdf-view-redisplay t)) + +(defun pdf-view-align-left () + "Align left edge of pdf with left edge of window." + (interactive) + (image-set-window-hscroll 0) + (pdf-view-redisplay t)) + +(defun pdf-view-align-right () + "Align right edge of pdf with right edge of window." + (interactive) + (image-set-window-hscroll + (/ (* (- (car (pdf-view-image-size)) + (window-pixel-width)) + (window-width)) + (window-pixel-width))) ; convert from pixel to character width + (pdf-view-redisplay t)) + + +;; * ================================================================== * +;; * Slicing +;; * ================================================================== * + +(defun pdf-view-set-slice (x y width height &optional window) + "Set the slice of the pages that should be displayed in WINDOW. + +WINDOW defaults to `selected-window' if not provided. +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) + "Set the slice from the page's bounding-box. + +WINDOW defaults to `selected-window' if not provided. + +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) + "Reset the current slice and redisplay WINDOW. + +WINDOW defaults to `selected-window' if not provided. + +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 + (progn (remove-hook 'pdf-view-change-page-hook + 'pdf-view-set-slice-from-bounding-box t) + (pdf-view-reset-slice))))) + + +;; * ================================================================== * +;; * Display +;; * ================================================================== * + +(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/org/elpa/pdf-tools-20220823.513/pdf-virtual.el b/org/elpa/pdf-tools-20220823.513/pdf-virtual.el new file mode 100644 index 0000000..613a966 --- /dev/null +++ b/org/elpa/pdf-tools-20220823.513/pdf-virtual.el @@ -0,0 +1,1039 @@ +;;; pdf-virtual.el --- Virtual PDF documents -*- lexical-binding: t; -*- + +;; Copyright (C) 2015 Andreas Politz + +;; Author: Andreas Politz +;; 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 . + +;;; 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) + +(declare-function pdf-view-mode "pdf-view.el") + +;; * ================================================================== * +;; * 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/org/elpa/tablist-20200427.2205/tablist-autoloads.el b/org/elpa/tablist-20200427.2205/tablist-autoloads.el new file mode 100644 index 0000000..5884389 --- /dev/null +++ b/org/elpa/tablist-20200427.2205/tablist-autoloads.el @@ -0,0 +1,60 @@ +;;; tablist-autoloads.el --- automatically extracted autoloads -*- lexical-binding: t -*- +;; +;;; 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. + +This is a minor mode. If called interactively, toggle the +`Tablist minor mode' mode. If the prefix argument is positive, +enable the mode, and if it is zero or negative, disable the mode. + +If called from Lisp, toggle the mode if ARG is `toggle'. Enable +the mode if ARG is nil, omitted, or is a positive number. +Disable the mode if ARG is a negative number. + +To check whether the minor mode is enabled in the current buffer, +evaluate `tablist-minor-mode'. + +The mode's hook is called both when the mode is enabled and when +it is disabled. + +\\{tablist-minor-mode-map} + +\(fn &optional ARG)" t nil) + +(autoload 'tablist-mode "tablist" "\ + + +\(fn)" t nil) + +(register-definition-prefixes "tablist" '("tablist-")) + +;;;*** + +;;;### (autoloads nil "tablist-filter" "tablist-filter.el" (0 0 0 +;;;;;; 0)) +;;; Generated autoloads from tablist-filter.el + +(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/org/elpa/tablist-20200427.2205/tablist-filter.el b/org/elpa/tablist-20200427.2205/tablist-filter.el new file mode 100644 index 0000000..c5d56b8 --- /dev/null +++ b/org/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 +;; 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 . + +;;; 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/org/elpa/tablist-20200427.2205/tablist-pkg.el b/org/elpa/tablist-20200427.2205/tablist-pkg.el new file mode 100644 index 0000000..7d62282 --- /dev/null +++ b/org/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/org/elpa/tablist-20200427.2205/tablist.el b/org/elpa/tablist-20200427.2205/tablist.el new file mode 100644 index 0000000..df8778b --- /dev/null +++ b/org/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 +;; 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 . + +;;; 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/org/init.el b/org/init.el index e15790c..721b254 100644 --- a/org/init.el +++ b/org/init.el @@ -21,6 +21,7 @@ ; add executables to path ahead of them being used by extensions / emacs stuff (add-to-list 'exec-path "C:/Users/mcros/OneDrive/Programs/PortableApps/sqlite3") (add-to-list 'exec-path "C:/msys64/usr/bin/unzip.exe") + (setenv "PATH" (concat "C:\\msys64\\mingw64\\bin;" (getenv "PATH"))) ) (when kmn/is-termux ; setup storage locations -- cheat so mobile/desktop look alike for file urls @@ -59,12 +60,13 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; additional packages (add-to-list 'package-selected-packages - '(nov) + '(nov pdf-tools) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Load misc extensions (require 'org) +(pdf-tools-install) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Update/add auto file handling diff --git a/org/nov-places b/org/nov-places new file mode 100644 index 0000000..d1557d3 --- /dev/null +++ b/org/nov-places @@ -0,0 +1 @@ +((b872b0f0-02f2-479f-ba38-19a4549716b8 (index . 16) (point . 56303))) \ No newline at end of file