added pdf-tools

This commit is contained in:
KemoNine 2022-08-25 11:49:25 -04:00
parent 285dbbfd5b
commit 7e00753126
68 changed files with 32514 additions and 1 deletions

View File

@ -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"

View File

@ -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 >~ / ~<remap> <scroll-right>~ |
| image-scroll-left | ~C-x <~ / ~<remap> <scroll-left>~ |
| image-scroll-up | ~C-v~ / ~<remap> <scroll-up>~ |
| image-scroll-down | ~M-v~ / ~<remap> <scroll-down>~ |
| image-forward-hscroll | ~C-f~ / ~right~ / ~<remap> <forward-char>~ |
| image-backward-hscroll | ~C-b~ / ~left~ / ~<remap> <backward-char>~ |
| image-bob | ~<remap> <beginning-of-buffer>~ |
| image-eob | ~<remap> <end-of-buffer>~ |
| image-bol | ~<remap> <move-beginning-of-line>~ |
| image-eol | ~<remap> <move-end-of-line>~ |
| image-scroll-down | ~<remap> <scroll-down>~ |
| image-scroll-up | ~<remap> <scroll-up>~ |
| image-scroll-left | ~<remap> <scroll-left>~ |
| image-scroll-right | ~<remap> <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!

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 <<EOF
usage:$(basename "$0") [--help|-n|-i DIR|[-d -D]|[--os OS]]
-n Don't do anything, but check if this OS is handled.
-i DIR Install the program in the given directory.
-d Force dependency installattion.
-D Skip dependency installattion.
--os OS Use the given OS's installer
--help Display this message.
EOF
exit "$1"
}
# Search for command $1 in PATH. Print its absolute filename.
which()
{
if [ -z "$1" ]; then
return 1
fi
command -v "$1"
}
# Quote $@ for the shell.
quote()
{
quoted=
for arg; do
qarg=$(printf "%s" "$arg" | sed -e 's/[|&;<>()$\`"'\'' ]/\\&/g')
if [ -z "$quoted" ]; then
quoted=$qarg
else
quoted="$quoted $qarg"
fi
done
printf "%s" "$quoted"
}
# Attempt to exec $@ as root.
exec_privileged() {
if [ -z "$1" ]; then
echo "internal error: command is empty"
exit 2
fi
if [ -w / ]; then
"$@"
elif which sudo >/dev/null 2>&1; then
sudo -- "$@"
retval=$?
sudo -k
return $retval
elif which su >/dev/null 2>&1; then
su -c "$(quote "$@")"
else
echo "No such program: sudo or su"
exit 1
fi
}
# Test if $1 is in PATH or exit with a failure status.
assert_program()
{
if ! which "$1" >/dev/null 2>&1; then
echo "No such program: $1"
exit 1
fi
}
# Source filename $1 and echo variable $2.
source_var()
{
if ! [ -f "$1" ] || ! [ -r "$1" ] || [ -z "$2" ]; then
return 1
fi
# shellcheck source=/dev/null
. "$1"
eval "printf '%s\n' \$$2"
return 0
}
exit_success()
{
echo "==========================="
echo " Build succeeded. :O) "
echo "==========================="
exit 0
}
exit_fail()
{
echo "==========================="
echo " Build failed. ;o( "
echo "==========================="
if [ -z "$PKG_INSTALL_FORCE" ]; then
echo "Note: maybe try the '-d' option."
fi
exit 1
}
# Return 0, if all required packages seem to be installed.
have_packages_installed()
{
{
which pkg-config || return 1
if ! [ -f configure ];then
which autoreconf || return 1
which automake || return 1
fi
for lib in libpng glib-2.0 poppler poppler-glib zlib; do
pkg-config --exists $lib || return 1
done
which make || return 1
which gcc || which cc || return 1
[ $? -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 "<nixpkgs>" &>/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:

View File

@ -0,0 +1,5 @@
#!/bin/sh
echo "Running autoreconf..."
autoreconf -i

View File

@ -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.h>
],[error (0, 0, "");])],
[AC_DEFINE([HAVE_ERROR_H],1, [Define to 1 if error.h is usable.])
AC_MSG_RESULT([yes])],
AC_MSG_RESULT([no]))
AC_LANG_POP([C])
CFLAGS=$SAVED_CFLAGS
# Checks for typedefs, structures, and compiler characteristics.
AC_TYPE_SIZE_T
AC_TYPE_SSIZE_T
AC_CHECK_TYPES([ptrdiff_t])
AC_C_BIGENDIAN
# Checks for library functions.
AC_FUNC_ERROR_AT_LINE
AC_FUNC_STRTOD
AC_CHECK_FUNCS([strcspn strtol getline])
AC_CONFIG_FILES([Makefile])
AC_OUTPUT
echo
echo "Is case-sensitive searching enabled ? ${HAVE_POPPLER_FIND_OPTS}"
echo "Is modifying text annotations enabled ? ${HAVE_POPPLER_ANNOT_WRITE}"
echo "Is modifying markup annotations enabled ? ${HAVE_POPPLER_ANNOT_MARKUP}"
echo

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,246 @@
// Copyright (C) 2013, 2014 Andreas Politz
// Author: Andreas Politz <politza@fh-trier.de>
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef _EPDF_H_
#define _EPDF_H_ _EPDF_H_
#include "config.h"
#include <glib.h>
#include <poppler.h>
#include <png.h>
/* Some library functions print warnings to stdout, inhibit it. */
#define DISCARD_STDOUT(saved_fd) \
do { \
int __fd; \
fflush(stdout); \
saved_fd = dup(1); \
__fd = open("/dev/null", O_WRONLY); \
dup2(__fd, 1); \
close(__fd); \
} while (0)
#define UNDISCARD_STDOUT(saved_fd) \
do { \
fflush(stdout); \
dup2(saved_fd, 1); \
close(saved_fd); \
} while (0)
/* Writing responses */
#define OK_BEGIN() \
do { \
puts("OK"); \
} while (0)
#define OK_END() \
do { \
puts("."); \
fflush (stdout); \
} while (0)
#define OK() \
do { \
puts ("OK\n."); \
fflush (stdout); \
} while (0)
/* Dealing with image data. */
#ifdef WORDS_BIGENDIAN
#define ARGB_TO_RGB(rgb, argb) \
do { \
rgb[0] = argb[1]; \
rgb[1] = argb[2]; \
rgb[2] = argb[3]; \
} while (0)
#define ARGB_EQUAL(argb1, argb2) \
(argb1[1] == argb2[1] \
&& argb1[2] == argb2[2] \
&& argb1[3] == argb2[3])
#else
#define ARGB_TO_RGB(rgb, argb) \
do { \
rgb[0] = argb[2]; \
rgb[1] = argb[1]; \
rgb[2] = argb[0]; \
} while (0)
#define ARGB_EQUAL(argb1, argb2) \
(argb1[0] == argb2[0] \
&& argb1[1] == argb2[1] \
&& argb1[2] == argb2[2])
#endif
#define NORMALIZE_PAGE_ARG(doc, first, last) \
*first = MAX(1, *first); \
if (*last <= 0) \
*last = poppler_document_get_n_pages (doc); \
else \
*last = MIN(*last, poppler_document_get_n_pages (doc));
/* png_jmpbuf is supposed to be not available in older versions of
libpng. */
#ifndef png_jmpbuf
# define png_jmpbuf(png_ptr) ((png_ptr)->jmpbuf)
#endif
#ifndef HAVE_ERROR_H
# define error(status, errno, fmt, args...) \
do { \
int error = (errno); \
fflush (stdout); \
fprintf (stderr, "%s: " fmt, PACKAGE_NAME, ## args); \
if (error) \
fprintf (stderr, ": %s", strerror (error)); \
fprintf (stderr, "\n"); \
exit (status); \
} while (0)
#endif
#define internal_error(fmt, args...) \
error (2, 0, "internal error in %s: " fmt, __func__, ## args)
#define error_if_not(expr) \
if (! (expr)) goto error;
#define perror_if_not(expr, fmt, args...) \
do { \
if (! (expr)) \
{ \
printf_error_response ((fmt), ## args); \
goto error; \
} \
} while (0)
#define cerror_if_not(expr, error_msg, fmt, args...) \
do { \
if (! (expr)) \
{ \
if (error_msg) \
*(error_msg) = g_strdup_printf((fmt), ## args); \
goto error; \
} \
} while (0)
/* Declare commands */
#define DEC_CMD(name) \
{#name, cmd_ ## name, cmd_ ## name ## _spec, \
G_N_ELEMENTS (cmd_ ## name ## _spec)}
#define DEC_CMD2(command, name) \
{name, cmd_ ## command, cmd_ ## command ## _spec, \
G_N_ELEMENTS (cmd_ ## command ## _spec)}
/* Declare option */
#define DEC_DOPT(name, type, sname) \
{name, type, offsetof (document_options_t, sname)}
enum suffix_char { NONE, COLON, NEWLINE};
enum image_type { PPM, PNG };
typedef struct
{
PopplerAnnotMapping *amap;
gchar *key;
} annotation_t;
typedef enum
{
ARG_INVALID = 0,
ARG_DOC,
ARG_BOOL,
ARG_STRING,
ARG_NONEMPTY_STRING,
ARG_NATNUM,
ARG_EDGE,
ARG_EDGE_OR_NEGATIVE,
ARG_EDGES,
ARG_EDGES_OR_POSITION,
ARG_COLOR,
ARG_REST
} command_arg_type_t;
typedef struct
{
const char *name;
command_arg_type_t type;
size_t offset;
} document_option_t;
typedef struct
{
PopplerColor bg, fg;
gboolean usecolors;
gboolean printed;
} render_options_t;
typedef struct
{
render_options_t render;
} document_options_t;
typedef struct
{
PopplerDocument *pdf;
char *filename;
char *passwd;
struct
{
GHashTable *keys; /* key => page */
GList **pages; /* page array */
} annotations;
document_options_t options;
} document_t;
typedef struct
{
command_arg_type_t type;
union
{
gboolean flag;
const char *string;
long natnum;
document_t *doc;
gdouble edge;
PopplerColor color;
PopplerRectangle rectangle;
#ifdef HAVE_POPPLER_ANNOT_MARKUP
PopplerQuadrilateral quadrilateral;
#endif
struct
{
char * const *args;
int nargs;
} rest;
} value;
} command_arg_t;
typedef struct
{
GHashTable *documents;
} epdfinfo_t;
typedef struct
{
const char *name;
void (* execute) (const epdfinfo_t *ctxt, const command_arg_t *args);
const command_arg_type_t *args_spec;
int nargs;
} command_t;
#endif /* _EPDF_H_ */

View File

@ -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}

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,3 @@
#include <stdio.h>
#define printf(fmt, args...) (fprintf (stderr, (fmt), ## args))
#define SYNCTEX_INLINE

View File

@ -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

View File

@ -0,0 +1,570 @@
/*
Copyright (c) 2008-2017 jerome DOT laurens AT u-bourgogne DOT fr
This file is part of the __SyncTeX__ package.
[//]: # (Latest Revision: Fri Jul 14 16:20:41 UTC 2017)
[//]: # (Version: 1.21)
See `synctex_parser_readme.md` for more details
## License
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE
Except as contained in this notice, the name of the copyright holder
shall not be used in advertising or otherwise to promote the sale,
use or other dealings in this Software without prior written
authorization from the copyright holder.
*/
/* In this file, we find all the functions that may depend on the operating system. */
#include <synctex_parser_utils.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <limits.h>
#include <ctype.h>
#include <string.h>
#include <sys/stat.h>
#if defined(_WIN32) || defined(__WIN32__) || defined(__TOS_WIN__) || defined(__WINDOWS__)
#define SYNCTEX_WINDOWS 1
#endif
#if defined(__OS2__)
#define SYNCTEX_OS2 1
#endif
#if defined(_WIN32)
#define SYNCTEX_RECENT_WINDOWS 1
#endif
#ifdef SYNCTEX_WINDOWS
#include <windows.h>
#include <shlwapi.h> /* Use shlwapi.lib */
#endif
void *_synctex_malloc(size_t size) {
void * ptr = malloc(size);
if(ptr) {
memset(ptr,0, size);/* ensures null termination of strings */
}
return (void *)ptr;
}
void _synctex_free(void * ptr) {
if (ptr) {
free(ptr);
}
}
#if !defined(_WIN32)
# include <syslog.h>
#endif
int _synctex_log(int level, const char * prompt, const char * reason,va_list arg) {
int result;
# ifdef SYNCTEX_RECENT_WINDOWS
{/* This code is contributed by William Blum.
As it does not work on some older computers,
the _WIN32 conditional here is replaced with a SYNCTEX_RECENT_WINDOWS one.
According to http://msdn.microsoft.com/en-us/library/aa363362(VS.85).aspx
Minimum supported client Windows 2000 Professional
Minimum supported server Windows 2000 Server
People running Windows 2K standard edition will not have OutputDebugStringA.
JL.*/
char *buff;
size_t len;
OutputDebugStringA(prompt);
len = _vscprintf(reason, arg) + 1;
buff = (char*)malloc( len * sizeof(char) );
result = vsprintf(buff, reason, arg) +strlen(prompt);
OutputDebugStringA(buff);
OutputDebugStringA("\n");
free(buff);
}
# elif SYNCTEX_USE_SYSLOG
char * buffer1 = NULL;
char * buffer2 = NULL;
openlog ("SyncTeX", LOG_CONS | LOG_PID | LOG_PERROR | LOG_NDELAY, LOG_LOCAL0);
if (vasprintf(&buffer1,reason,arg)>=0
&& asprintf(&buffer2,"%s%s",prompt, buffer1)>=0) {
syslog (level, "%s", buffer2);
result = (int)strlen(buffer2);
} else {
syslog (level, "%s",prompt);
vsyslog(level,reason,arg);
result = (int)strlen(prompt);
}
free(buffer1);
free(buffer2);
closelog();
# else
FILE * where = level == LOG_ERR? stderr: stdout;
result = fputs(prompt,where);
result += vfprintf(where, reason, arg);
result += fprintf(where,"\n");
# endif
return result;
}
int _synctex_error(const char * reason,...) {
va_list arg;
int result;
va_start (arg, reason);
#if defined(SYNCTEX_RECENT_WINDOWS) /* LOG_ERR is not used */
result = _synctex_log(0, "! SyncTeX Error : ", reason, arg);
#else
result = _synctex_log(LOG_ERR, "! SyncTeX Error : ", reason, arg);
#endif
va_end (arg);
return result;
}
int _synctex_debug(const char * reason,...) {
va_list arg;
int result;
va_start (arg, reason);
#if defined(SYNCTEX_RECENT_WINDOWS) /* LOG_DEBUG is not used */
result = _synctex_log(0, "! SyncTeX Error : ", reason, arg);
#else
result = _synctex_log(LOG_DEBUG, "! SyncTeX Error : ", reason, arg);
#endif
va_end (arg);
return result;
}
/* strip the last extension of the given string, this string is modified! */
void _synctex_strip_last_path_extension(char * string) {
if(NULL != string){
char * last_component = NULL;
char * last_extension = NULL;
# if defined(SYNCTEX_WINDOWS)
last_component = PathFindFileName(string);
last_extension = PathFindExtension(string);
if(last_extension == NULL)return;
if(last_component == NULL)last_component = string;
if(last_extension>last_component){/* filter out paths like "my/dir/.hidden" */
last_extension[0] = '\0';
}
# else
char * next = NULL;
/* first we find the last path component */
if(NULL == (last_component = strstr(string,"/"))){
last_component = string;
} else {
++last_component;
while((next = strstr(last_component,"/"))){
last_component = next+1;
}
}
# if defined(SYNCTEX_OS2)
/* On OS2, the '\' is also a path separator. */
while((next = strstr(last_component,"\\"))){
last_component = next+1;
}
# endif /* SYNCTEX_OS2 */
/* then we find the last path extension */
if((last_extension = strstr(last_component,"."))){
++last_extension;
while((next = strstr(last_extension,"."))){
last_extension = next+1;
}
--last_extension;/* back to the "." */
if(last_extension>last_component){/* filter out paths like ....my/dir/.hidden"*/
last_extension[0] = '\0';
}
}
# endif /* SYNCTEX_WINDOWS */
}
}
synctex_bool_t synctex_ignore_leading_dot_slash_in_path(const char ** name_ref)
{
if (SYNCTEX_IS_DOT((*name_ref)[0]) && SYNCTEX_IS_PATH_SEPARATOR((*name_ref)[1])) {
do {
(*name_ref) += 2;
while (SYNCTEX_IS_PATH_SEPARATOR((*name_ref)[0])) {
++(*name_ref);
}
} while(SYNCTEX_IS_DOT((*name_ref)[0]) && SYNCTEX_IS_PATH_SEPARATOR((*name_ref)[1]));
return synctex_YES;
}
return synctex_NO;
}
/* The base name is necessary to deal with the 2011 file naming convention...
* path is a '\0' terminated string
* The return value is the trailing part of the argument,
* just following the first occurrence of the regexp pattern "[^|/|\].[\|/]+".*/
const char * _synctex_base_name(const char *path) {
const char * ptr = path;
do {
if (synctex_ignore_leading_dot_slash_in_path(&ptr)) {
return ptr;
}
do {
if (!*(++ptr)) {
return path;
}
} while (!SYNCTEX_IS_PATH_SEPARATOR(*ptr));
} while (*(++ptr));
return path;
}
/* Compare two file names, windows is sometimes case insensitive... */
synctex_bool_t _synctex_is_equivalent_file_name(const char *lhs, const char *rhs) {
/* Remove the leading regex '(\./+)*' in both rhs and lhs */
synctex_ignore_leading_dot_slash_in_path(&lhs);
synctex_ignore_leading_dot_slash_in_path(&rhs);
next_character:
if (SYNCTEX_IS_PATH_SEPARATOR(*lhs)) {/* lhs points to a path separator */
if (!SYNCTEX_IS_PATH_SEPARATOR(*rhs)) {/* but not rhs */
return synctex_NO;
}
++lhs;
++rhs;
synctex_ignore_leading_dot_slash_in_path(&lhs);
synctex_ignore_leading_dot_slash_in_path(&rhs);
goto next_character;
} else if (SYNCTEX_IS_PATH_SEPARATOR(*rhs)) {/* rhs points to a path separator but not lhs */
return synctex_NO;
} else if (SYNCTEX_ARE_PATH_CHARACTERS_EQUAL(*lhs,*rhs)){/* uppercase do not match */
return synctex_NO;
} else if (!*lhs) {/* lhs is at the end of the string */
return *rhs ? synctex_NO : synctex_YES;
} else if(!*rhs) {/* rhs is at the end of the string but not lhs */
return synctex_NO;
}
++lhs;
++rhs;
goto next_character;
}
synctex_bool_t _synctex_path_is_absolute(const char * name) {
if(!strlen(name)) {
return synctex_NO;
}
# if defined(SYNCTEX_WINDOWS) || defined(SYNCTEX_OS2)
if(strlen(name)>2) {
return (name[1]==':' && SYNCTEX_IS_PATH_SEPARATOR(name[2]))?synctex_YES:synctex_NO;
}
return synctex_NO;
# else
return SYNCTEX_IS_PATH_SEPARATOR(name[0])?synctex_YES:synctex_NO;
# endif
}
/* We do not take care of UTF-8 */
const char * _synctex_last_path_component(const char * name) {
const char * c = name+strlen(name);
if(c>name) {
if(!SYNCTEX_IS_PATH_SEPARATOR(*c)) {
do {
--c;
if(SYNCTEX_IS_PATH_SEPARATOR(*c)) {
return c+1;
}
} while(c>name);
}
return c;/* the last path component is the void string*/
}
return c;
}
int _synctex_copy_with_quoting_last_path_component(const char * src, char ** dest_ref, size_t size) {
if(src && dest_ref) {
const char * lpc;
# define dest (*dest_ref)
dest = NULL; /* Default behavior: no change and success. */
lpc = _synctex_last_path_component(src);
if(strlen(lpc)) {
if(strchr(lpc,' ') && lpc[0]!='"' && lpc[strlen(lpc)-1]!='"') {
/* We are in the situation where adding the quotes is allowed. */
/* Time to add the quotes. */
/* Consistency test: we must have dest+size>dest+strlen(dest)+2
* or equivalently: strlen(dest)+2<size (see below) */
if(strlen(src)<size) {
if((dest = (char *)malloc(size+2))) {
char * dpc = dest + (lpc-src); /* dpc is the last path component of dest. */
if(dest != strncpy(dest,src,size)) {
_synctex_error("! _synctex_copy_with_quoting_last_path_component: Copy problem");
free(dest);
dest = NULL;/* Don't forget to reinitialize. */
return -2;
}
memmove(dpc+1,dpc,strlen(dpc)+1); /* Also move the null terminating character. */
dpc[0]='"';
dpc[strlen(dpc)+1]='\0';/* Consistency test */
dpc[strlen(dpc)]='"';
return 0; /* Success. */
}
return -1; /* Memory allocation error. */
}
_synctex_error("! _synctex_copy_with_quoting_last_path_component: Internal inconsistency");
return -3;
}
return 0; /* Success. */
}
return 0; /* No last path component. */
# undef dest
}
return 1; /* Bad parameter, this value is subject to changes. */
}
/* The client is responsible of the management of the returned string, if any. */
char * _synctex_merge_strings(const char * first,...);
char * _synctex_merge_strings(const char * first,...) {
va_list arg;
size_t size = 0;
const char * temp;
/* First retrieve the size necessary to store the merged string */
va_start (arg, first);
temp = first;
do {
size_t len = strlen(temp);
if(UINT_MAX-len<size) {
_synctex_error("! _synctex_merge_strings: Capacity exceeded.");
return NULL;
}
size+=len;
} while( (temp = va_arg(arg, const char *)) != NULL);
va_end(arg);
if(size>0) {
char * result = NULL;
++size;
/* Create the memory storage */
if(NULL!=(result = (char *)malloc(size))) {
char * dest = result;
va_start (arg, first);
temp = first;
do {
if((size = strlen(temp))>0) {
/* There is something to merge */
if(dest != strncpy(dest,temp,size)) {
_synctex_error("! _synctex_merge_strings: Copy problem");
free(result);
result = NULL;
return NULL;
}
dest += size;
}
} while( (temp = va_arg(arg, const char *)) != NULL);
va_end(arg);
dest[0]='\0';/* Terminate the merged string */
return result;
}
_synctex_error("! _synctex_merge_strings: Memory problem");
return NULL;
}
return NULL;
}
/* The purpose of _synctex_get_name is to find the name of the synctex file.
* There is a list of possible filenames from which we return the most recent one and try to remove all the others.
* With two runs of pdftex or xetex we are sure the the synctex file is really the most appropriate.
*/
int _synctex_get_name(const char * output, const char * build_directory, char ** synctex_name_ref, synctex_io_mode_t * io_mode_ref)
{
if(output && synctex_name_ref && io_mode_ref) {
/* If output is already absolute, we just have to manage the quotes and the compress mode */
size_t size = 0;
char * synctex_name = NULL;
synctex_io_mode_t io_mode = *io_mode_ref;
const char * base_name = _synctex_last_path_component(output); /* do not free, output is the owner. base name of output*/
/* Do we have a real base name ? */
if(strlen(base_name)>0) {
/* Yes, we do. */
const char * temp = NULL;
char * core_name = NULL; /* base name of output without path extension. */
char * dir_name = NULL; /* dir name of output */
char * quoted_core_name = NULL;
char * basic_name = NULL;
char * gz_name = NULL;
char * quoted_name = NULL;
char * quoted_gz_name = NULL;
char * build_name = NULL;
char * build_gz_name = NULL;
char * build_quoted_name = NULL;
char * build_quoted_gz_name = NULL;
struct stat buf;
time_t the_time = 0;
/* Create core_name: let temp point to the dot before the path extension of base_name;
* We start form the \0 terminating character and scan the string upward until we find a dot.
* The leading dot is not accepted. */
if((temp = strrchr(base_name,'.')) && (size = temp - base_name)>0) {
/* There is a dot and it is not at the leading position */
if(NULL == (core_name = (char *)malloc(size+1))) {
_synctex_error("! _synctex_get_name: Memory problem 1");
return -1;
}
if(core_name != strncpy(core_name,base_name,size)) {
_synctex_error("! _synctex_get_name: Copy problem 1");
free(core_name);
dir_name = NULL;
return -2;
}
core_name[size] = '\0';
} else {
/* There is no path extension,
* Just make a copy of base_name */
core_name = _synctex_merge_strings(base_name);
}
/* core_name is properly set up, owned by "self". */
/* creating dir_name. */
size = strlen(output)-strlen(base_name);
if(size>0) {
/* output contains more than one path component */
if(NULL == (dir_name = (char *)malloc(size+1))) {
_synctex_error("! _synctex_get_name: Memory problem");
free(core_name);
return -1;
}
if(dir_name != strncpy(dir_name,output,size)) {
_synctex_error("! _synctex_get_name: Copy problem");
free(dir_name);
dir_name = NULL;
free(core_name);
dir_name = NULL;
return -2;
}
dir_name[size] = '\0';
}
/* dir_name is properly set up. It ends with a path separator, if non void. */
/* creating quoted_core_name. */
if(strchr(core_name,' ')) {
quoted_core_name = _synctex_merge_strings("\"",core_name,"\"");
}
/* quoted_core_name is properly set up. */
if(dir_name &&strlen(dir_name)>0) {
basic_name = _synctex_merge_strings(dir_name,core_name,synctex_suffix,NULL);
if(quoted_core_name && strlen(quoted_core_name)>0) {
quoted_name = _synctex_merge_strings(dir_name,quoted_core_name,synctex_suffix,NULL);
}
} else {
basic_name = _synctex_merge_strings(core_name,synctex_suffix,NULL);
if(quoted_core_name && strlen(quoted_core_name)>0) {
quoted_name = _synctex_merge_strings(quoted_core_name,synctex_suffix,NULL);
}
}
if(!_synctex_path_is_absolute(output) && build_directory && (size = strlen(build_directory))) {
temp = build_directory + size - 1;
if(_synctex_path_is_absolute(temp)) {
build_name = _synctex_merge_strings(build_directory,basic_name,NULL);
if(quoted_core_name && strlen(quoted_core_name)>0) {
build_quoted_name = _synctex_merge_strings(build_directory,quoted_name,NULL);
}
} else {
build_name = _synctex_merge_strings(build_directory,"/",basic_name,NULL);
if(quoted_core_name && strlen(quoted_core_name)>0) {
build_quoted_name = _synctex_merge_strings(build_directory,"/",quoted_name,NULL);
}
}
}
if(basic_name) {
gz_name = _synctex_merge_strings(basic_name,synctex_suffix_gz,NULL);
}
if(quoted_name) {
quoted_gz_name = _synctex_merge_strings(quoted_name,synctex_suffix_gz,NULL);
}
if(build_name) {
build_gz_name = _synctex_merge_strings(build_name,synctex_suffix_gz,NULL);
}
if(build_quoted_name) {
build_quoted_gz_name = _synctex_merge_strings(build_quoted_name,synctex_suffix_gz,NULL);
}
/* All the others names are properly set up... */
/* retain the most recently modified file */
# define TEST(FILENAME,COMPRESS_MODE) \
if(FILENAME) {\
if (stat(FILENAME, &buf)) { \
free(FILENAME);\
FILENAME = NULL;\
} else if (buf.st_mtime>the_time) { \
the_time=buf.st_mtime; \
synctex_name = FILENAME; \
if (COMPRESS_MODE) { \
io_mode |= synctex_io_gz_mask; \
} else { \
io_mode &= ~synctex_io_gz_mask; \
} \
} \
}
TEST(basic_name,synctex_DONT_COMPRESS);
TEST(gz_name,synctex_COMPRESS);
TEST(quoted_name,synctex_DONT_COMPRESS);
TEST(quoted_gz_name,synctex_COMPRESS);
TEST(build_name,synctex_DONT_COMPRESS);
TEST(build_gz_name,synctex_COMPRESS);
TEST(build_quoted_name,synctex_DONT_COMPRESS);
TEST(build_quoted_gz_name,synctex_COMPRESS);
# undef TEST
/* Free all the intermediate filenames, except the one that will be used as returned value. */
# define CLEAN_AND_REMOVE(FILENAME) \
if(FILENAME && (FILENAME!=synctex_name)) {\
remove(FILENAME);\
printf("synctex tool info: %s removed\n",FILENAME);\
free(FILENAME);\
FILENAME = NULL;\
}
CLEAN_AND_REMOVE(basic_name);
CLEAN_AND_REMOVE(gz_name);
CLEAN_AND_REMOVE(quoted_name);
CLEAN_AND_REMOVE(quoted_gz_name);
CLEAN_AND_REMOVE(build_name);
CLEAN_AND_REMOVE(build_gz_name);
CLEAN_AND_REMOVE(build_quoted_name);
CLEAN_AND_REMOVE(build_quoted_gz_name);
# undef CLEAN_AND_REMOVE
/* set up the returned values */
* synctex_name_ref = synctex_name;
/* synctex_name won't always end in .gz, even when compressed. */
FILE * F = fopen(synctex_name, "r");
if (F != NULL) {
if (!feof(F)
&& 31 == fgetc(F)
&& !feof(F)
&& 139 == fgetc(F)) {
io_mode = synctex_compress_mode_gz;
}
fclose(F);
}
* io_mode_ref = io_mode;
return 0;
}
return -1;/* bad argument */
}
return -2;
}
const char * _synctex_get_io_mode_name(synctex_io_mode_t io_mode) {
static const char * synctex_io_modes[4] = {"r","rb","a","ab"};
unsigned index = ((io_mode & synctex_io_gz_mask)?1:0) + ((io_mode & synctex_io_append_mask)?2:0);// bug pointed out by Jose Alliste
return synctex_io_modes[index];
}

View File

@ -0,0 +1,163 @@
/*
Copyright (c) 2008-2017 jerome DOT laurens AT u-bourgogne DOT fr
This file is part of the __SyncTeX__ package.
[//]: # (Latest Revision: Fri Jul 14 16:20:41 UTC 2017)
[//]: # (Version: 1.21)
See `synctex_parser_readme.md` for more details
## License
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE
Except as contained in this notice, the name of the copyright holder
shall not be used in advertising or otherwise to promote the sale,
use or other dealings in this Software without prior written
authorization from the copyright holder.
*/
#ifndef SYNCTEX_PARSER_UTILS_H
#define SYNCTEX_PARSER_UTILS_H
/* The utilities declared here are subject to conditional implementation.
* All the operating system special stuff goes here.
* The problem mainly comes from file name management: path separator, encoding...
*/
#include "synctex_version.h"
typedef int synctex_bool_t;
# define synctex_YES (0==0)
# define synctex_NO (0==1)
# define synctex_ADD_QUOTES -1
# define synctex_COMPRESS -1
# define synctex_DONT_ADD_QUOTES 0
# define synctex_DONT_COMPRESS 0
#ifndef __SYNCTEX_PARSER_UTILS__
# define __SYNCTEX_PARSER_UTILS__
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
# if defined(_WIN32) || defined(__OS2__)
# define SYNCTEX_CASE_SENSITIVE_PATH 0
# define SYNCTEX_IS_PATH_SEPARATOR(c) ('/' == c || '\\' == c)
# else
# define SYNCTEX_CASE_SENSITIVE_PATH 1
# define SYNCTEX_IS_PATH_SEPARATOR(c) ('/' == c)
# endif
# if defined(_WIN32) || defined(__OS2__)
# define SYNCTEX_IS_DOT(c) ('.' == c)
# else
# define SYNCTEX_IS_DOT(c) ('.' == c)
# endif
# if SYNCTEX_CASE_SENSITIVE_PATH
# define SYNCTEX_ARE_PATH_CHARACTERS_EQUAL(left,right) (left != right)
# else
# define SYNCTEX_ARE_PATH_CHARACTERS_EQUAL(left,right) (toupper(left) != toupper(right))
# endif
/* This custom malloc functions initializes to 0 the newly allocated memory.
* There is no bzero function on windows. */
void *_synctex_malloc(size_t size);
/* To balance _synctex_malloc.
* ptr might be NULL. */
void _synctex_free(void * ptr);
/* This is used to log some informational message to the standard error stream.
* On Windows, the stderr stream is not exposed and another method is used.
* The return value is the number of characters printed. */
int _synctex_error(const char * reason,...);
int _synctex_debug(const char * reason,...);
/* strip the last extension of the given string, this string is modified!
* This function depends on the OS because the path separator may differ.
* This should be discussed more precisely. */
void _synctex_strip_last_path_extension(char * string);
/* Compare two file names, windows is sometimes case insensitive...
* The given strings may differ stricto sensu, but represent the same file name.
* It might not be the real way of doing things.
* The return value is an undefined non 0 value when the two file names are equivalent.
* It is 0 otherwise. */
synctex_bool_t _synctex_is_equivalent_file_name(const char *lhs, const char *rhs);
/* Description forthcoming.*/
synctex_bool_t _synctex_path_is_absolute(const char * name);
/* Description forthcoming...*/
const char * _synctex_last_path_component(const char * name);
/* Description forthcoming...*/
const char * _synctex_base_name(const char *path);
/* If the core of the last path component of src is not already enclosed with double quotes ('"')
* and contains a space character (' '), then a new buffer is created, the src is copied and quotes are added.
* In all other cases, no destination buffer is created and the src is not copied.
* 0 on success, which means no error, something non 0 means error, mainly due to memory allocation failure, or bad parameter.
* This is used to fix a bug in the first version of pdftex with synctex (1.40.9) for which names with spaces
* were not managed in a standard way.
* On success, the caller owns the buffer pointed to by dest_ref (is any) and
* is responsible of freeing the memory when done.
* The size argument is the size of the src buffer. On return the dest_ref points to a buffer sized size+2.*/
int _synctex_copy_with_quoting_last_path_component(const char * src, char ** dest_ref, size_t size);
/* These are the possible extensions of the synctex file */
extern const char * synctex_suffix;
extern const char * synctex_suffix_gz;
typedef unsigned int synctex_io_mode_t;
typedef enum {
synctex_io_append_mask = 1,
synctex_io_gz_mask = synctex_io_append_mask<<1
} synctex_io_mode_masks_t;
typedef enum {
synctex_compress_mode_none = 0,
synctex_compress_mode_gz = 1
} synctex_compress_mode_t;
int _synctex_get_name(const char * output, const char * build_directory, char ** synctex_name_ref, synctex_io_mode_t * io_mode_ref);
/* returns the correct mode required by fopen and gzopen from the given io_mode */
const char * _synctex_get_io_mode_name(synctex_io_mode_t io_mode);
synctex_bool_t synctex_ignore_leading_dot_slash_in_path(const char ** name);
#ifdef __cplusplus
}
#endif
#endif
#endif /* SYNCTEX_PARSER_UTILS_H */

View File

@ -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

View File

@ -0,0 +1,2 @@
*.Dockerfile
*.build

View File

@ -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)"

View File

@ -0,0 +1,9 @@
#!/bin/bash
# Step over prompts from the package-manager.
if [ -f /etc/arch-release ]; then
yes ''
else
yes
fi

View File

@ -0,0 +1,4 @@
ADD . /epdfinfo
WORKDIR /epdfinfo
RUN make -s distclean || true
CMD ["sh", "./test/docker/lib/run-tests"]

View File

@ -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.

View File

@ -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?

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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 <politza@fh-trier.de>
;; Keywords: files, doc-view, pdf
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;
;;; Code:
;;
(require 'pdf-macs)
(require 'pdf-info)
(require 'pdf-util)
;; * ================================================================== *
;; * Customiazations
;; * ================================================================== *
(defcustom pdf-cache-image-limit 64
"Maximum number of cached PNG images per buffer."
:type 'integer
:group 'pdf-cache
:group 'pdf-view)
(defcustom pdf-cache-prefetch-delay 0.5
"Idle time in seconds before prefetching images starts."
:group 'pdf-view
:type 'number)
(defcustom pdf-cache-prefetch-pages-function
'pdf-cache-prefetch-pages-function-default
"A function returning a list of pages to be prefetched.
It is called with no arguments in the PDF window and should
return a list of page-numbers, determining the pages that should
be prefetched and their order."
:group 'pdf-view
:type 'function)
;; * ================================================================== *
;; * Simple Value cache
;; * ================================================================== *
(defvar-local pdf-cache--data nil)
(defvar pdf-annot-modified-functions)
(defun pdf-cache--initialize ()
"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

View File

@ -0,0 +1,89 @@
;;; pdf-dev.el --- Mother's little development helper -*- lexical-binding: t; -*-
;; Copyright (C) 2015 Andreas Politz
;; Author: Andreas Politz <politza@hochschule-trier.de>
;; Keywords:
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;
;; This file is only meant for developers. The entry point is
;; pdf-dev-minor-mode, which see.
;;; Code:
(defvar pdf-dev-root-directory
(file-name-directory
(directory-file-name
(file-name-directory load-file-name))))
(defun pdf-dev-reload ()
"Reload Lisp files from source."
(interactive)
(let ((default-directory (expand-file-name
"lisp"
pdf-dev-root-directory))
loaded)
(dolist (file (directory-files default-directory nil "\\`pdf-\\w*\\.el\\'"))
(push file loaded)
(load-file file))
(message "Loaded %s" (mapconcat 'identity loaded " "))))
(define-minor-mode pdf-dev-minor-mode
"Make developing pdf-tools easier.
It does the following:
Quits the server and sets `pdf-info-epdfinfo-program' to
../server/epdfinfo.
Installs a `compilation-finish-functions' which will restart
epdfinfo after a successful recompilation.
Sets up `load-path' and reloads all PDF Tools Lisp files."
:group 'pdf-dev
(let ((lisp-dir (expand-file-name "lisp" pdf-dev-root-directory)))
(setq load-path (remove lisp-dir load-path))
(cond
(pdf-dev-minor-mode
(add-hook 'compilation-finish-functions 'pdf-dev-compilation-finished)
(add-to-list 'load-path lisp-dir)
(setq pdf-info-epdfinfo-program
(expand-file-name
"epdfinfo"
(expand-file-name "server" pdf-dev-root-directory)))
(pdf-info-quit)
(pdf-dev-reload))
(t
(remove-hook 'compilation-finish-functions 'pdf-dev-compilation-finished)))))
(defun pdf-dev-compilation-finished (buffer status)
"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

View File

@ -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 <politza@fh-trier.de>
;; Keywords: files, multimedia
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;
(require 'pdf-view)
(require 'pdf-util)
;;; Code:
(defgroup pdf-history nil
"A simple stack-based history."
:group 'pdf-tools)
(defvar-local pdf-history-stack nil
"The stack of history items.")
(defvar-local pdf-history-index nil
"The current index into the `pdf-history-stack'.")
(defvar pdf-history-minor-mode-map
(let ((kmap (make-sparse-keymap)))
(define-key kmap (kbd "B") #'pdf-history-backward)
(define-key kmap (kbd "N") #'pdf-history-forward)
(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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,832 @@
;;; pdf-isearch.el --- Isearch in pdf buffers. -*- lexical-binding: t -*-
;; Copyright (C) 2013, 2014 Andreas Politz
;; Author: Andreas Politz <politza@fh-trier.de>
;; Keywords: files, multimedia
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;
;;; Todo:
;;
;; * Add the possibility to limit the search to a range of pages.
(require 'cl-lib)
(require 'pdf-util)
(require 'pdf-info)
(require 'pdf-misc)
(require 'pdf-view)
(require 'pdf-cache)
(require 'let-alist)
;;; Code:
;; * ================================================================== *
;; * Customizations
;; * ================================================================== *
(defgroup pdf-isearch nil
"Isearch in pdf buffers."
:group 'pdf-tools)
(defface pdf-isearch-match
'((((background dark)) (:inherit isearch))
(((background light)) (:inherit isearch)))
"Face used to determine the colors of the current match."
:group 'pdf-isearch
:group 'pdf-tools-faces)
(defface pdf-isearch-lazy
'((((background dark)) (:inherit lazy-highlight))
(((background light)) (:inherit lazy-highlight)))
"Face used to determine the colors of non-current matches."
:group 'pdf-isearch
:group 'pdf-tools-faces)
(defface pdf-isearch-batch
'((((background dark)) (:inherit match))
(((background light)) (:inherit match)))
"Face used to determine the colors in `pdf-isearch-batch-mode'."
:group 'pdf-isearch
:group 'pdf-tools-faces)
(defcustom pdf-isearch-hyphenation-character "-­"
"Characters used as hyphens when word searching."
:group 'pdf-isearch
:type 'string)
(defvar pdf-isearch-search-fun-function nil
"Search function used when searching.
Like `isearch-search-fun-function', though it should return a
function \(FN STRING &optional PAGES\), which in turn should
return a result like `pdf-info-search-regexp'.")
;; * ================================================================== *
;; * Internal Variables
;; * ================================================================== *
(defvar-local pdf-isearch-current-page nil
"The page that is currently searched.")
(defvar-local pdf-isearch-current-match nil
"A list ((LEFT TOP RIGHT BOT) ...) of the current match or nil.
A match may contain more than one edges-element, e.g. when regexp
searching across multiple lines.")
(defvar-local pdf-isearch-current-matches nil
"A list of matches of the last search.")
(defvar-local pdf-isearch-current-parameter nil
"A list of search parameter \(search-string regex-p case-fold word-search\).")
;; * ================================================================== *
;; * Modes
;; * ================================================================== *
(declare-function pdf-occur "pdf-occur.el")
(declare-function pdf-sync-backward-search "pdf-sync.el")
(defvar pdf-isearch-minor-mode-map
(let ((kmap (make-sparse-keymap)))
(define-key kmap [remap occur] 'pdf-occur)
kmap)
"Keymap used in `pdf-isearch-minor-mode'.")
(defvar pdf-isearch-active-mode-map
(let ((kmap (make-sparse-keymap)))
(set-keymap-parent kmap isearch-mode-map)
(define-key kmap (kbd "C-d") 'pdf-view-dark-minor-mode)
(define-key kmap (kbd "C-b") 'pdf-isearch-batch-mode)
(define-key kmap (kbd "M-s o") 'pdf-isearch-occur)
(define-key kmap (kbd "M-s s") 'pdf-isearch-sync-backward)
kmap)
"Keymap used in `pdf-isearch-active-mode'.
This keymap is used, when isearching in PDF buffers. Its parent
keymap is `isearch-mode-map'.")
(put 'image-scroll-up 'isearch-scroll t)
(put 'image-scroll-down 'isearch-scroll t)
(define-minor-mode pdf-isearch-active-mode
"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:

View File

@ -0,0 +1,379 @@
;;; pdf-links.el --- Handle PDF links. -*- lexical-binding: t -*-
;; Copyright (C) 2013, 2014 Andreas Politz
;; Author: Andreas Politz <politza@fh-trier.de>
;; Keywords: files, multimedia
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;
(require 'pdf-info)
(require 'pdf-util)
(require 'pdf-misc)
(require 'pdf-cache)
(require 'pdf-isearch)
(require 'let-alist)
(require 'org)
;;; Code:
;; * ================================================================== *
;; * Customizations
;; * ================================================================== *
(defgroup pdf-links nil
"Following links in PDF documents."
:group 'pdf-tools)
(defface pdf-links-read-link
'((((background dark)) (:background "red" :foreground "yellow"))
(((background light)) (:background "red" :foreground "yellow")))
"Face used to determine the colors when reading links."
;; :group 'pdf-links
:group 'pdf-tools-faces)
(defcustom pdf-links-read-link-convert-commands
'(;;"-font" "FreeMono"
"-pointsize" "%P"
"-undercolor" "%f"
"-fill" "%b"
"-draw" "text %X,%Y '%c'")
"The commands for the convert program, when decorating links for reading.
See `pdf-util-convert' for an explanation of the format.
Aside from the description there, two additional escape chars are
available.
%P -- The scaled font pointsize, i.e. IMAGE-WIDTH * SCALE (See
`pdf-links-convert-pointsize-scale').
%c -- String describing the current link key (e.g. AA, AB,
etc.)."
:group 'pdf-links
:type '(repeat string)
:link '(variable-link pdf-isearch-convert-commands)
:link '(url-link "http://www.imagemagick.org/script/convert.php"))
(defcustom pdf-links-convert-pointsize-scale 0.01
"The scale factor for the -pointsize convert command.
This determines the relative size of the font, when interactively
reading links."
:group 'pdf-links
:type '(restricted-sexp :match-alternatives
((lambda (x) (and (numberp x)
(<= x 1)
(>= x 0))))))
(defcustom pdf-links-browse-uri-function
'pdf-links-browse-uri-default
"The function for handling uri links.
This function should accept one argument, the URI to follow, and
do something with it."
:group 'pdf-links
:type 'function)
;; * ================================================================== *
;; * Minor Mode
;; * ================================================================== *
(defvar pdf-links-minor-mode-map
(let ((kmap (make-sparse-keymap)))
(define-key kmap (kbd "f") 'pdf-links-isearch-link)
(define-key kmap (kbd "F") 'pdf-links-action-perform)
kmap))
;;;###autoload
(define-minor-mode pdf-links-minor-mode
"Handle links in PDF documents.\\<pdf-links-minor-mode-map>
If this mode is enabled, most links in the document may be
activated by clicking on them or by pressing \\[pdf-links-action-perform] and selecting
one of the displayed keys, or by using isearch limited to
links via \\[pdf-links-isearch-link].
\\{pdf-links-minor-mode-map}"
:group 'pdf-links
(pdf-util-assert-pdf-buffer)
(cond
(pdf-links-minor-mode
(pdf-view-add-hotspot-function 'pdf-links-hotspots-function 0))
(t
(pdf-view-remove-hotspot-function 'pdf-links-hotspots-function)))
(pdf-view-redisplay t))
(defun pdf-links-hotspots-function (page size)
"Create hotspots for links on PAGE using SIZE."
(let ((links (pdf-cache-pagelinks page))
(id-fmt "link-%d-%d")
(i 0)
(pointer 'hand)
hotspots)
(dolist (l links)
(let ((e (pdf-util-scale
(cdr (assq 'edges l)) size 'round))
(id (intern (format id-fmt page
(cl-incf i)))))
(push `((rect . ((,(nth 0 e) . ,(nth 1 e))
. (,(nth 2 e) . ,(nth 3 e))))
,id
(pointer
,pointer
help-echo ,(pdf-links-action-to-string l)))
hotspots)
(local-set-key
(vector id 'mouse-1)
(lambda nil
(interactive "@")
(pdf-links-action-perform l)))
(local-set-key
(vector id t)
'pdf-util-image-map-mouse-event-proxy)))
(nreverse hotspots)))
(defun pdf-links-action-to-string (link)
"Return a string representation of ACTION."
(let-alist link
(concat
(cl-case .type
(goto-dest
(if (> .page 0)
(format "Goto page %d" .page)
"Destination not found"))
(goto-remote
(if (and .filename (file-exists-p .filename))
(format "Goto %sfile '%s'"
(if (> .page 0)
(format "p.%d of " .page)
"")
.filename)
(format "Link to nonexistent file '%s'" .filename)))
(uri
(if (> (length .uri) 0)
(format "Link to uri '%s'" .uri)
(format "Link to empty uri")))
(t (format "Unrecognized link type: %s" .type)))
(if (> (length .title) 0)
(format " (%s)" .title)))))
;;;###autoload
(defun pdf-links-action-perform (link)
"Follow LINK, depending on its type.
This may turn to another page, switch to another PDF buffer or
invoke `pdf-links-browse-uri-function'.
Interactively, link is read via `pdf-links-read-link-action'.
This function displays characters around the links in the current
page and starts reading characters (ignoring case). After a
sufficient number of characters have been read, the corresponding
link's link is invoked. Additionally, SPC may be used to
scroll the current page."
(interactive
(list (or (pdf-links-read-link-action "Activate link (SPC scrolls): ")
(error "No link selected"))))
(let-alist link
(cl-case .type
((goto-dest goto-remote)
(let ((window (selected-window)))
(cl-case .type
(goto-dest
(unless (> .page 0)
(error "Link points to nowhere")))
(goto-remote
(unless (and .filename (file-exists-p .filename))
(error "Link points to nonexistent file %s" .filename))
(setq window (display-buffer
(or (find-buffer-visiting .filename)
(find-file-noselect .filename))))))
(with-selected-window window
(when (derived-mode-p 'pdf-view-mode)
(when (> .page 0)
(pdf-view-goto-page .page))
(when .top
;; Showing the tooltip delays displaying the page for
;; some reason (sit-for/redisplay don't help), do it
;; later.
(run-with-idle-timer 0.001 nil
(lambda ()
(when (window-live-p window)
(with-selected-window window
(when (derived-mode-p 'pdf-view-mode)
(pdf-util-tooltip-arrow .top)))))))))))
(uri
(funcall pdf-links-browse-uri-function .uri))
(t
(error "Unrecognized link type: %s" .type)))
nil))
(defun pdf-links-read-link-action (prompt)
"Using PROMPT, interactively read a link-action.
See `pdf-links-action-perform' for the interface."
(pdf-util-assert-pdf-window)
(let* ((links (pdf-cache-pagelinks
(pdf-view-current-page)))
(keys (pdf-links-read-link-action--create-keys
(length links)))
(key-strings (mapcar (apply-partially 'apply 'string)
keys))
(alist (cl-mapcar 'cons keys links))
(size (pdf-view-image-size))
(colors (pdf-util-face-colors
'pdf-links-read-link pdf-view-dark-minor-mode))
(args (list
:foreground (car colors)
:background (cdr colors)
:formats
`((?c . ,(lambda (_edges) (pop key-strings)))
(?P . ,(number-to-string
(max 1 (* (cdr size)
pdf-links-convert-pointsize-scale)))))
:commands pdf-links-read-link-convert-commands
:apply (pdf-util-scale-relative-to-pixel
(mapcar (lambda (l) (cdr (assq 'edges l)))
links)))))
(unless links
(error "No links on this page"))
(unwind-protect
(let ((image-data
(pdf-cache-get-image
(pdf-view-current-page)
(car size) (car size) 'pdf-links-read-link-action)))
(unless image-data
(setq image-data (apply 'pdf-util-convert-page args ))
(pdf-cache-put-image
(pdf-view-current-page)
(car size) image-data 'pdf-links-read-link-action))
(pdf-view-display-image
(create-image image-data (pdf-view-image-type) t))
(pdf-links-read-link-action--read-chars prompt alist))
(pdf-view-redisplay))))
(defun pdf-links-read-link-action--read-chars (prompt alist)
(catch 'done
(let (key)
(while t
(let* ((chars (append (mapcar 'caar alist)
(mapcar 'downcase (mapcar 'caar alist))
(list ?\s)))
(ch (read-char-choice prompt chars)))
(setq ch (upcase ch))
(cond
((= ch ?\s)
(when (= (window-vscroll) (image-scroll-up))
(image-scroll-down (window-vscroll))))
(t
(setq alist (delq nil (mapcar (lambda (elt)
(and (eq ch (caar elt))
(cons (cdar elt)
(cdr elt))))
alist))
key (append key (list ch))
prompt (concat prompt (list ch)))
(when (= (length alist) 1)
(message nil)
(throw 'done (cdar alist))))))))))
(defun pdf-links-read-link-action--create-keys (n)
(when (> n 0)
(let ((len (1+ (floor (log n 26))))
keys)
(dotimes (i n)
(let (key)
(dotimes (_x len)
(push (+ (% i 26) ?A) key)
(setq i (/ i 26)))
(push key keys)))
(nreverse keys))))
(defun pdf-links-isearch-link ()
(interactive)
(let* (quit-p
(isearch-mode-end-hook
(cons (lambda nil
(setq quit-p isearch-mode-end-hook-quit))
isearch-mode-end-hook))
(pdf-isearch-filter-matches-function
'pdf-links-isearch-link-filter-matches)
(pdf-isearch-narrow-to-page t)
(isearch-message-prefix-add "(Links)")
pdf-isearch-batch-mode)
(isearch-forward)
(unless (or quit-p (null pdf-isearch-current-match))
(let* ((page (pdf-view-current-page))
(match (car pdf-isearch-current-match))
(size (pdf-view-image-size))
(links (sort (cl-remove-if
(lambda (e)
(= 0 (pdf-util-edges-intersection-area (car e) match)))
(mapcar (lambda (l)
(cons (pdf-util-scale (alist-get 'edges l) size)
l))
(pdf-cache-pagelinks page)))
(lambda (e1 e2)
(> (pdf-util-edges-intersection-area
(alist-get 'edges e1) match)
(pdf-util-edges-intersection-area
(alist-get 'edges e2) match))))))
(unless links
(error "No link found at this position"))
(pdf-links-action-perform (car links))))))
(defun pdf-links-isearch-link-filter-matches (matches)
(let ((links (pdf-util-scale
(mapcar (apply-partially 'alist-get 'edges)
(pdf-cache-pagelinks
(pdf-view-current-page)))
(pdf-view-image-size))))
(cl-remove-if-not
(lambda (m)
(cl-some
(lambda (edges)
(cl-some (lambda (link)
(pdf-util-with-edges (link edges)
(let ((area (min (* link-width link-height)
(* edges-width edges-height))))
(> (/ (pdf-util-edges-intersection-area edges link)
(float area)) 0.5))))
links))
m))
matches)))
(defun pdf-links-browse-uri-default (uri)
"Open the string URI using Org.
Wraps the URI in \[\[ ... \]\] and calls `org-open-link-from-string'
on the resulting string."
(cl-check-type uri string)
(message "Opening `%s' with Org" uri)
(cond
((fboundp 'org-link-open-from-string)
(org-link-open-from-string (format "[[%s]]" uri)))
;; For Org 9.2 and older
((fboundp 'org-open-link-from-string)
(org-open-link-from-string (format "[[%s]]" uri)))))
(provide 'pdf-links)
;;; pdf-links.el ends here

View File

@ -0,0 +1,80 @@
;;; pdf-loader.el --- Minimal PDF Tools loader -*- lexical-binding: t; -*-
;; Copyright (C) 2017 Andreas Politz
;; Author: Andreas Politz <politza@hochschule-trier.de>
;; Keywords:
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;
;;; Code:
(defconst pdf-loader--auto-mode-alist-item
(copy-sequence "\\.[pP][dD][fF]\\'")
"The item used in `auto-mode-alist'.")
(defconst pdf-loader--magic-mode-alist-item
(copy-sequence "%PDF")
"The item used in`magic-mode-alist'.")
(declare-function pdf-tools-install "pdf-tools.el")
;;;###autoload
(defun pdf-loader-install (&optional no-query-p skip-dependencies-p
no-error-p force-dependencies-p)
"Prepare Emacs for using PDF Tools.
This function acts as a replacement for `pdf-tools-install' and
makes Emacs load and use PDF Tools as soon as a PDF file is
opened, but not sooner.
The arguments are passed verbatim to `pdf-tools-install', which
see."
(let ((args (list no-query-p skip-dependencies-p
no-error-p force-dependencies-p)))
(if (featurep 'pdf-tools)
(apply #'pdf-tools-install args)
(pdf-loader--install
(lambda ()
(apply #'pdf-loader--load args))))))
(defun pdf-loader--load (&rest args)
(pdf-loader--uninstall)
(save-selected-window
(pdf-tools-install args)))
(defun pdf-loader--install (loader)
(pdf-loader--uninstall)
(push (cons pdf-loader--auto-mode-alist-item loader)
auto-mode-alist)
(push (cons pdf-loader--magic-mode-alist-item loader)
magic-mode-alist))
(defun pdf-loader--uninstall ()
(let ((elt (assoc pdf-loader--auto-mode-alist-item
auto-mode-alist)))
(when elt
(setq auto-mode-alist (remove elt auto-mode-alist))))
(let ((elt (assoc pdf-loader--magic-mode-alist-item
magic-mode-alist)))
(when elt
(setq magic-mode-alist (remove elt magic-mode-alist)))))
(provide 'pdf-loader)
;;; pdf-loader.el ends here

View File

@ -0,0 +1,51 @@
;;; pdf-macs.el --- Macros for pdf-tools. -*- lexical-binding:t -*-
;; Copyright (C) 2013 Andreas Politz
;; Author: Andreas Politz <politza@fh-trier.de>
;; Keywords: files, doc-view, pdf
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;
;;; Code:
;;
(defmacro pdf-view-current-page (&optional window)
;;TODO: write documentation!
`(image-mode-window-get 'page ,window))
(defmacro pdf-view-current-overlay (&optional window)
;;TODO: write documentation!
`(image-mode-window-get 'overlay ,window))
(defmacro pdf-view-current-image (&optional window)
;;TODO: write documentation!
`(image-mode-window-get 'image ,window))
(defmacro pdf-view-current-slice (&optional window)
;;TODO: write documentation!
`(image-mode-window-get 'slice ,window))
(defmacro pdf-view-current-window-size (&optional window)
;;TODO: write documentation!
`(image-mode-window-get 'window-size ,window))
(defmacro pdf-view-window-needs-redisplay (&optional window)
`(image-mode-window-get 'needs-redisplay ,window))
(provide 'pdf-macs)
;;; pdf-macs.el ends here

View File

@ -0,0 +1,300 @@
;;; pdf-misc.el --- Miscellaneous commands for PDF buffer. -*- lexical-binding: t; -*-
;; Copyright (C) 2013, 2014 Andreas Politz
;; Author: Andreas Politz <politza@fh-trier.de>
;; Keywords: files, multimedia
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;
(require '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

View File

@ -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 <politza@fh-trier.de>
;; Keywords: files, multimedia
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;
(require 'pdf-tools)
(require 'pdf-view)
(require 'pdf-util)
(require 'pdf-info)
(require 'pdf-isearch)
(require 'tablist)
(require 'ibuf-ext)
(require 'dired)
(require 'let-alist)
;;; Code:
;; * ================================================================== *
;; * Custom & Variables
;; * ================================================================== *
(defgroup pdf-occur nil
"Display matching lines of PDF documents."
:group 'pdf-tools)
(defface pdf-occur-document-face
'((default (:inherit font-lock-string-face)))
"Face used to highlight documents in the list buffer.")
(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`. \\<pdf-occur-buffer-mode-map>
Some useful keys are:
\\[pdf-occur-abort-search] - Abort the search.
\\[pdf-occur-revert-buffer-with-args] - Restart the search.
\\[universal-argument] \\[pdf-occur-revert-buffer-with-args] - Restart search with different regexp.
\\[universal-argument] \\[universal-argument] \\[pdf-occur-revert-buffer-with-args] - Same, but do a plain string search.
\\[tablist-push-regexp-filter] - Filter matches by regexp on current or prefix-th column.
\\[tablist-pop-filter] - Remove last added filter.
\\[pdf-occur-tablist-do-delete] - Remove the current file from the search.
\\[pdf-occur-tablist-gather-documents] - Include marked files from displayed `dired'/`ibuffer' and
`pdf-view-mode' buffers in the search.
\\{pdf-occur-buffer-mode-map}"
(setq-local case-fold-search case-fold-search)
(setq-local next-error-function #'pdf-occur-next-error)
(setq-local revert-buffer-function
#'pdf-occur-revert-buffer)
(setq next-error-last-buffer (current-buffer))
(setq-local tabulated-list-sort-key nil)
(setq-local tabulated-list-use-header-line t)
(setq-local tablist-operations-function
(lambda (op &rest _)
(cl-case op
(supported-operations '(find-entry))
(find-entry
(let ((display-buffer-overriding-action
'(display-buffer-same-window)))
(pdf-occur-goto-occurrence)))))))
;;;###autoload
(defun pdf-occur (string &optional regexp-p)
"List lines matching STRING or PCRE.
Interactively search for a regexp. Unless a prefix arg was given,
in which case this functions performs a string search.
If `pdf-occur-prefer-string-search' is non-nil, the meaning of
the prefix-arg is inverted."
(interactive
(progn
(pdf-util-assert-pdf-buffer)
(list
(pdf-occur-read-string
(pdf-occur-want-regexp-search-p))
(pdf-occur-want-regexp-search-p))))
(pdf-util-assert-pdf-buffer)
(pdf-occur-search (list (current-buffer)) string regexp-p))
(defvar ibuffer-filtering-qualifiers)
;;;###autoload
(defun pdf-occur-multi-command ()
"Perform `pdf-occur' on multiple buffer.
For a programmatic search of multiple documents see
`pdf-occur-search'."
(interactive)
(ibuffer)
(with-current-buffer "*Ibuffer*"
(pdf-occur-ibuffer-minor-mode)
(unless (member '(derived-mode . pdf-view-mode)
ibuffer-filtering-qualifiers)
(ibuffer-filter-by-derived-mode 'pdf-view-mode))
(message
"%s"
(substitute-command-keys
"Mark a bunch of PDF buffers and type \\[pdf-occur-ibuffer-do-occur]"))
(sit-for 3)))
(defun pdf-occur-revert-buffer (&rest _)
"Restart the search."
(pdf-occur-assert-occur-buffer-p)
(unless pdf-occur-search-documents
(error "No documents to search"))
(unless pdf-occur-search-string
(error "Nothing to search for"))
(let* ((2-columns-p (= 1 (length pdf-occur-search-documents)))
(filename-width
(min 24
(apply #'max
(mapcar #'length
(mapcar #'pdf-occur-abbrev-document
(mapcar #'car pdf-occur-search-documents))))))
(page-sorter (tablist-generate-sorter
(if 2-columns-p 0 1)
'<
'string-to-number)))
(setq tabulated-list-format
(if 2-columns-p
`[("Page" 4 ,page-sorter :right-align t)
("Line" 0 t)]
`[("Document" ,filename-width t)
("Page" 4 ,page-sorter :right-align t)
("Line" 0 t)])
tabulated-list-entries nil))
(tabulated-list-revert)
(pdf-occur-start-search
pdf-occur-search-documents
pdf-occur-search-string
pdf-occur-search-regexp-p)
(pdf-occur-update-header-line)
(setq mode-line-process
'(:propertize ":run" face compilation-mode-line-run)))
(defun pdf-occur-revert-buffer-with-args (string &optional regexp-p documents)
"Restart the search with modified arguments.
Interactively just restart the search, unless a prefix was given.
In this case read a new search string. With `C-u C-u' as prefix
additionally invert the current state of
`pdf-occur-search-regexp-p'."
(interactive
(progn
(pdf-occur-assert-occur-buffer-p)
(cond
(current-prefix-arg
(let ((regexp-p
(if (equal current-prefix-arg '(16))
(not pdf-occur-search-regexp-p)
pdf-occur-search-regexp-p)))
(list
(pdf-occur-read-string regexp-p)
regexp-p)))
(t
(list pdf-occur-search-string
pdf-occur-search-regexp-p)))))
(setq pdf-occur-search-string string
pdf-occur-search-regexp-p regexp-p)
(when documents
(setq pdf-occur-search-documents
(pdf-occur-normalize-documents documents)))
(pdf-occur-revert-buffer))
(defun pdf-occur-abort-search ()
"Abort the current search.
This immediately kills the search process."
(interactive)
(unless (pdf-occur-search-in-progress-p)
(user-error "No search in progress"))
(pdf-info-kill-local-server)
(pdf-occur-search-finished t))
;; * ================================================================== *
;; * Finding occurrences
;; * ================================================================== *
(defun pdf-occur-goto-occurrence (&optional no-select-window-p)
"Go to the occurrence at point.
If EVENT is nil, use occurrence at current line. Select the
PDF's window, unless NO-SELECT-WINDOW-P is non-nil.
FIXME: EVENT not used at the moment."
(interactive)
(let ((item (tabulated-list-get-id)))
(when item
(let* ((doc (plist-get item :document))
(page (plist-get item :page))
(match (plist-get item :match-edges))
(buffer (if (bufferp doc)
doc
(or (find-buffer-visiting doc)
(find-file-noselect doc))))
window)
(if no-select-window-p
(setq window (display-buffer buffer))
(pop-to-buffer buffer)
(setq window (selected-window)))
(with-selected-window window
(when page
(pdf-view-goto-page page))
;; Abuse isearch.
(when match
(let ((pixel-match
(pdf-util-scale-relative-to-pixel match))
(pdf-isearch-batch-mode t))
(pdf-isearch-hl-matches pixel-match nil t)
(pdf-isearch-focus-match-batch pixel-match))))))))
(defun pdf-occur-view-occurrence (&optional _event)
"View the occurrence at EVENT.
If EVENT is nil, use occurrence at current line."
(interactive (list last-nonmenu-event))
(pdf-occur-goto-occurrence t))
(defun pdf-occur-next-error (&optional arg reset)
"Move to the Nth (default 1) next match in an PDF Occur mode buffer.
Compatibility function for \\[next-error] invocations."
(interactive "p")
;; we need to run pdf-occur-find-match from within the Occur buffer
(with-current-buffer
;; Choose the buffer and make it current.
(if (next-error-buffer-p (current-buffer))
(current-buffer)
(next-error-find-buffer
nil nil
(lambda ()
(eq major-mode 'pdf-occur-buffer-mode))))
(when (bobp)
(setq reset t))
(if reset
(goto-char (point-min))
(beginning-of-line))
(when (/= arg 0)
(when (eobp)
(forward-line -1))
(when reset
(cl-decf arg))
(let ((line (line-number-at-pos))
(limit (line-number-at-pos
(if (>= arg 0)
(1- (point-max))
(point-min)))))
(when (= line limit)
(error "No more matches"))
(forward-line
(if (>= arg 0)
(min arg (- limit line))
(max arg (- limit line))))))
;; In case the *Occur* buffer is visible in a nonselected window.
(tablist-move-to-major-column)
(let ((win (get-buffer-window (current-buffer) t)))
(if win (set-window-point win (point))))
(pdf-occur-goto-occurrence)))
;; * ================================================================== *
;; * Integration with other modes
;; * ================================================================== *
;;;###autoload
(define-minor-mode pdf-occur-global-minor-mode
"Enable integration of Pdf Occur with other modes.
This global minor mode enables (or disables)
`pdf-occur-ibuffer-minor-mode' and `pdf-occur-dired-minor-mode'
in all current and future ibuffer/dired buffer."
:global t
(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

View File

@ -0,0 +1,599 @@
;;; pdf-outline.el --- Outline for PDF buffer -*- lexical-binding: t -*-
;; Copyright (C) 2013, 2014 Andreas Politz
;; Author: Andreas Politz <politza@fh-trier.de>
;; Keywords: files, multimedia
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;
(require 'outline)
(require 'pdf-links)
(require 'pdf-view)
(require 'pdf-util)
(require 'cl-lib)
(require 'imenu)
(require 'let-alist)
;;; Code:
;;
;; User options
;;
(defgroup pdf-outline nil
"Display a navigatable outline of a PDF document."
:group 'pdf-tools)
(defcustom pdf-outline-buffer-indent 2
"The level of indent in the Outline buffer."
:type 'integer)
(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:

View File

@ -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 <politza@fh-trier.de>
;; Keywords: files, doc-view, pdf
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;
;; The backward search uses a heuristic, which is pretty simple, but
;; effective: It extracts the text around the click-position in the
;; PDF, normalizes 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.
\\<pdf-sync-minor-mode-map>
This works via SyncTeX, which means the TeX sources need to have
been compiled with `--synctex=1'. In AUCTeX this can be done by
setting `TeX-source-correlate-method' to 'synctex \(before AUCTeX
is loaded\) and enabling `TeX-source-correlate-mode'.
Then \\[pdf-sync-backward-search-mouse] in the PDF buffer will open the
corresponding TeX location.
If AUCTeX is your preferred tex-mode, this library arranges to
bind `pdf-sync-forward-display-pdf-key' \(the default is `C-c C-g'\)
to `pdf-sync-forward-search' in `TeX-source-correlate-map'. This
function displays the PDF page corresponding to the current
position in the TeX buffer. This function only works together
with AUCTeX."
:group 'pdf-sync
(pdf-util-assert-pdf-buffer))
;; * ================================================================== *
;; * Backward search (PDF -> TeX)
;; * ================================================================== *
(defcustom pdf-sync-backward-use-heuristic t
"Whether to apply a heuristic when backward searching.
If nil, just go where Synctex tells us. Otherwise try to find
the exact location of the clicked-upon text in the PDF."
: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

View File

@ -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.\\<pdf-links-minor-mode-map>
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.
\\<pdf-sync-minor-mode-map>
This works via SyncTeX, which means the TeX sources need to have
been compiled with `--synctex=1'. In AUCTeX this can be done by
setting `TeX-source-correlate-method' to 'synctex (before AUCTeX
is loaded) and enabling `TeX-source-correlate-mode'.
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

View File

@ -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:

View File

@ -0,0 +1,544 @@
;;; pdf-tools.el --- Support library for PDF documents -*- lexical-binding:t -*-
;; Copyright (C) 2013, 2014 Andreas Politz
;; Author: Andreas Politz <mail@andreas-politz.de>
;; Maintainer: Vedang Manerikar <vedang.manerikar@gmail.com>
;; 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 <http://www.gnu.org/licenses/>.
;;; Commentary:
;;
;; PDF Tools is, among other things, a replacement of DocView for PDF
;; files. The key difference is, that pages are not prerendered by
;; e.g. ghostscript and stored in the file-system, but rather created
;; on-demand and stored in memory.
;;
;; Note: This package is built and tested on GNU/Linux systems. It
;; works on macOS and Windows, but is officially supported only on
;; GNU/Linux systems. This package will not make macOS or Windows
;; specific functionality changes, behaviour on these systems is
;; provided as-is.
;;
;; Note: If you ever update it, you need to restart Emacs afterwards.
;;
;; To activate the package put
;;
;; (pdf-tools-install)
;;
;; somewhere in your .emacs.el .
;;
;; M-x pdf-tools-help RET
;;
;; gives some help on using the package and
;;
;; M-x pdf-tools-customize RET
;;
;; offers some customization options.
;; Features:
;;
;; * View
;; View PDF documents in a buffer with DocView-like bindings.
;;
;; * Isearch
;; Interactively search PDF documents like any other buffer. (Though
;; there is currently no regexp support.)
;;
;; * Follow links
;; Click on highlighted links, moving to some part of a different
;; page, some external file, a website or any other URI. Links may
;; also be followed by keyboard commands.
;;
;; * Annotations
;; Display and list text and markup annotations (like underline),
;; edit their contents and attributes (e.g. color), move them around,
;; delete them or create new ones and then save the modifications
;; back to the PDF file.
;;
;; * Attachments
;; Save files attached to the PDF-file or list them in a Dired buffer.
;;
;; * Outline
;; Use imenu or a special buffer to examine and navigate the PDF's
;; outline.
;;
;; * SyncTeX
;; Jump from a position on a page directly to the TeX source and
;; vice-versa.
;;
;; * Misc
;; + Display PDF's metadata.
;; + Mark a region and kill the text from the PDF.
;; + Search for occurrences of a string.
;; + Keep track of visited pages via a history.
;;; Code:
(require 'pdf-view)
(require 'pdf-util)
(require 'pdf-info)
(require 'cus-edit)
(require 'compile)
(require 'cl-lib)
(require 'package)
;; * ================================================================== *
;; * Customizables
;; * ================================================================== *
(defgroup pdf-tools nil
"Support library for PDF documents."
:group 'data)
(defgroup pdf-tools-faces nil
"Faces determining the colors used in the pdf-tools package.
In order to customize dark and light colors use
`pdf-tools-customize-faces', or set `custom-face-default-form' to
'all."
:group 'pdf-tools)
(defconst pdf-tools-modes
'(pdf-history-minor-mode
pdf-isearch-minor-mode
pdf-links-minor-mode
pdf-misc-minor-mode
pdf-outline-minor-mode
pdf-misc-size-indication-minor-mode
pdf-misc-menu-bar-minor-mode
pdf-annot-minor-mode
pdf-sync-minor-mode
pdf-misc-context-menu-minor-mode
pdf-cache-prefetch-minor-mode
pdf-view-auto-slice-minor-mode
pdf-occur-global-minor-mode
pdf-virtual-global-minor-mode))
(defcustom pdf-tools-enabled-modes
'(pdf-history-minor-mode
pdf-isearch-minor-mode
pdf-links-minor-mode
pdf-misc-minor-mode
pdf-outline-minor-mode
pdf-misc-size-indication-minor-mode
pdf-misc-menu-bar-minor-mode
pdf-annot-minor-mode
pdf-sync-minor-mode
pdf-misc-context-menu-minor-mode
pdf-cache-prefetch-minor-mode
pdf-occur-global-minor-mode
;; pdf-virtual-global-minor-mode
)
"A list of automatically enabled minor-modes.
PDF Tools is build as a series of minor-modes. This variable and
the function `pdf-tools-install' merely serve as a convenient
wrapper in order to load these modes in current and newly created
PDF buffers."
:group 'pdf-tools
:type `(set ,@(mapcar (lambda (mode)
`(function-item ,mode))
pdf-tools-modes)))
(defcustom pdf-tools-enabled-hook nil
"A hook ran after PDF Tools is enabled in a buffer."
:group 'pdf-tools
:type 'hook)
(defconst pdf-tools-auto-mode-alist-entry
'("\\.[pP][dD][fF]\\'" . pdf-view-mode)
"The entry to use for `auto-mode-alist'.")
(defconst pdf-tools-magic-mode-alist-entry
'("%PDF" . pdf-view-mode)
"The entry to use for `magic-mode-alist'.")
(defun pdf-tools-customize ()
"Customize Pdf Tools."
(interactive)
(customize-group 'pdf-tools))
(defun pdf-tools-customize-faces ()
"Customize PDF Tool's faces."
(interactive)
(let ((buffer (format "*Customize Group: %s*"
(custom-unlispify-tag-name 'pdf-tools-faces))))
(when (buffer-live-p (get-buffer buffer))
(with-current-buffer (get-buffer buffer)
(rename-uniquely)))
(customize-group 'pdf-tools-faces)
(with-current-buffer buffer
(set (make-local-variable 'custom-face-default-form) 'all))))
;; * ================================================================== *
;; * Installation
;; * ================================================================== *
;;;###autoload
(defcustom pdf-tools-handle-upgrades t
"Whether PDF Tools should handle upgrading itself."
:group 'pdf-tools
:type 'boolean)
(make-obsolete-variable 'pdf-tools-handle-upgrades
"Not used anymore" "0.90")
(defconst pdf-tools-directory
(or (and load-file-name
(file-name-directory load-file-name))
default-directory)
"The directory from where this library was first loaded.")
(defvar pdf-tools-msys2-directory nil)
(defcustom pdf-tools-installer-os nil
"Specifies which installer to use.
If nil the installer is chosen automatically. This variable is
useful if you have multiple installers present on your
system (e.g. nix on arch linux)"
:group 'pdf-tools
:type 'string)
(defun pdf-tools-identify-build-directory (directory)
"Return non-nil, if DIRECTORY appears to contain the epdfinfo source.
Returns the expanded directory-name of DIRECTORY or nil."
(setq directory (file-name-as-directory
(expand-file-name directory)))
(and (file-exists-p (expand-file-name "autobuild" directory))
(file-exists-p (expand-file-name "epdfinfo.c" directory))
directory))
(defun pdf-tools-locate-build-directory ()
"Attempt to locate a source directory.
Returns a appropriate directory or nil. See also
`pdf-tools-identify-build-directory'."
(cl-some #'pdf-tools-identify-build-directory
(list default-directory
(expand-file-name "build/server" pdf-tools-directory)
(expand-file-name "server")
(expand-file-name "server" pdf-tools-directory)
(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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -0,0 +1,464 @@
;;; tablist-filter.el --- Filter expressions for tablists. -*- lexical-binding:t -*-
;; Copyright (C) 2013, 2014 Andreas Politz
;; Author: Andreas Politz <politza@fh-trier.de>
;; Keywords: extensions, lisp
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;
(defvar python-mode-hook)
(let (python-mode-hook) ;FIXME: Why?
(require 'semantic/wisent/comp)
(require 'semantic/wisent/wisent))
;;; Code:
(defvar wisent-eoi-term)
(declare-function wisent-parse "semantic/wisent/wisent.el")
;;
;; *Variables
;;
(defvar tablist-filter-binary-operator
'((== . tablist-filter-op-equal)
(=~ . tablist-filter-op-regexp)
(< . tablist-filter-op-<)
(> . tablist-filter-op->)
(<= . tablist-filter-op-<=)
(>= . tablist-filter-op->=)
(= . tablist-filter-op-=)))
(defvar tablist-filter-unary-operator nil)
(defvar tablist-filter-wisent-parser nil)
(defvar tablist-filter-lexer-regexps nil)
(defvar tablist-filter-wisent-grammar
'(
;; terminals
;; Use lowercase for better looking error messages.
(operand unary-operator binary-operator or and not)
;; terminal associativity & precedence
((left binary-operator)
(left unary-operator)
(left or)
(left and)
(left not))
;; rules
(filter-or-empty
((nil))
((?\( ?\)) nil)
((filter) $1))
(filter
((operand) $1) ;;Named filter
((operand binary-operator operand) `(,(intern $2) ,$1 ,$3))
((unary-operator operand) `(,(intern $1) ,$2))
((not filter) `(not ,$2))
((filter and filter) `(and ,$1 ,$3))
((filter or filter) `(or ,$1 ,$3))
((?\( filter ?\)) $2))))
;;
;; *Filter Parsing
;;
(defun tablist-filter-parser-init (&optional reinitialize interactive)
(interactive (list t t))
(unless (and tablist-filter-lexer-regexps
(not reinitialize))
(let ((re (mapcar
(lambda (l)
(let ((re (regexp-opt
(mapcar 'symbol-name
(mapcar 'car l)) t)))
(if (= (length re) 0)
".\\`" ;;matches nothing
re)))
(list tablist-filter-binary-operator
tablist-filter-unary-operator))))
(setq tablist-filter-lexer-regexps
(nreverse
(cons (concat "\\(?:" (car re) "\\|" (cadr re)
"\\|[ \t\f\r\n\"!()]\\|&&\\|||\\)")
re)))))
(unless (and tablist-filter-wisent-parser
(not reinitialize))
(let ((wisent-compile-grammar*
(symbol-function
'wisent-compile-grammar)))
(setq tablist-filter-wisent-parser
;; Trick the byte-compile into not using the byte-compile
;; handler in semantic/wisent/comp.el, since it does not
;; always work (wisent-context-compile-grammar n/a).
(funcall wisent-compile-grammar*
tablist-filter-wisent-grammar))))
(when interactive
(message "Parser reinitialized."))
nil)
(defun tablist-filter-wisent-lexer ()
(cl-destructuring-bind (unary-op binary-op keywords)
tablist-filter-lexer-regexps
(skip-chars-forward " \t\f\r\n")
(cond
((eobp) (list wisent-eoi-term))
((= ?\" (char-after))
`(operand , (condition-case err
(read (current-buffer))
(error (signal (car err) (cons
"invalid lisp string"
(cdr err)))))))
((looking-at unary-op)
(goto-char (match-end 0))
`(unary-operator ,(match-string-no-properties 0)))
((looking-at binary-op)
(goto-char (match-end 0))
`(binary-operator ,(match-string-no-properties 0)))
((looking-at "&&")
(forward-char 2)
`(and "&&"))
((looking-at "||")
(forward-char 2)
`(or "||"))
((= ?! (char-after))
(forward-char)
`(not "!"))
((= ?\( (char-after))
(forward-char)
`(?\( "("))
((= ?\) (char-after))
(forward-char)
`(?\) ")"))
(t
(let ((beg (point)))
(when (re-search-forward keywords nil 'move)
(goto-char (match-beginning 0)))
`(operand ,(buffer-substring-no-properties
beg
(point))))))))
(defun tablist-filter-parse (filter)
(interactive "sFilter: ")
(tablist-filter-parser-init)
(with-temp-buffer
(save-excursion (insert filter))
(condition-case error
(wisent-parse tablist-filter-wisent-parser
'tablist-filter-wisent-lexer
(lambda (msg)
(signal 'error
(replace-regexp-in-string
"\\$EOI" "end of input"
msg t t))))
(error
(signal 'error
(append (if (consp (cdr error))
(cdr error)
(list (cdr error)))
(list (point))))))))
(defun tablist-filter-unparse (filter &optional noerror)
(cl-labels
((unparse (filter &optional noerror)
(cond
((stringp filter)
(if (or (string-match (nth 2 tablist-filter-lexer-regexps)
filter)
(= 0 (length filter)))
(format "%S" filter)
filter))
((and (eq (car-safe filter) 'not)
(= (length filter) 2))
(let ((paren (memq (car-safe (nth 1 filter)) '(or and))))
(format "!%s%s%s"
(if paren "(" "")
(unparse (cadr filter) noerror)
(if paren ")" ""))))
((and (memq (car-safe filter) '(and or))
(= (length filter) 3))
(let ((lparen (and (eq (car filter) 'and)
(eq 'or (car-safe (car-safe (cdr filter))))))
(rparen (and (eq (car filter) 'and)
(eq 'or (car-safe (car-safe (cddr filter)))))))
(format "%s%s%s %s %s%s%s"
(if lparen "(" "")
(unparse (cadr filter) noerror)
(if lparen ")" "")
(cl-case (car filter)
(and "&&") (or "||"))
(if rparen "(" "")
(unparse (car (cddr filter)) noerror)
(if rparen ")" ""))))
((and (assq (car-safe filter) tablist-filter-binary-operator)
(= (length filter) 3))
(format "%s %s %s"
(unparse (cadr filter) noerror)
(car filter)
(unparse (car (cddr filter)) noerror)))
((and (assq (car-safe filter) tablist-filter-unary-operator)
(= (length filter) 2))
(format "%s %s"
(car filter)
(unparse (cadr filter) noerror)))
((not filter) "")
(t (funcall (if noerror 'format 'error)
"Invalid filter: %s" filter)))))
(tablist-filter-parser-init)
(unparse filter noerror)))
(defun tablist-filter-eval (filter id entry &optional named-alist)
(cl-labels
((feval (filter)
(pcase filter
(`(not . ,(and operand (guard (not (cdr operand)))))
(not (feval (car operand))))
(`(and . ,(and operands (guard (= 2 (length operands)))))
(and
(feval (nth 0 operands))
(feval (nth 1 operands))))
(`(or . ,(and operands (guard (= 2 (length operands)))))
(or
(feval (nth 0 operands))
(feval (nth 1 operands))))
(`(,op . ,(and operands (guard (= (length operands) 1))))
(let ((fn (assq op tablist-filter-unary-operator)))
(unless fn
(error "Undefined unary operator: %s" op))
(funcall fn id entry (car operands))))
(`(,op . ,(and operands (guard (= (length operands) 2))))
(let ((fn (cdr (assq op tablist-filter-binary-operator))))
(unless fn
(error "Undefined binary operator: %s" op))
(funcall fn id entry (car operands)
(cadr operands))))
((guard (stringp filter))
(let ((fn (cdr (assoc filter named-alist))))
(unless fn
(error "Undefined named filter: %s" filter))
(if (functionp fn)
(funcall fn id entry))
(feval
(if (stringp fn) (tablist-filter-unparse fn) fn))))
(`nil t)
(_ (error "Invalid filter: %s" filter)))))
(feval filter)))
;;
;; *Filter Operators
;;
(defun tablist-filter-get-item-by-name (entry col-name)
(let* ((col (cl-position col-name tabulated-list-format
:key 'car
:test
(lambda (s1 s2)
(eq t (compare-strings
s1 nil nil s2 nil nil t)))))
(item (and col (elt entry col))))
(unless col
(error "No such column: %s" col-name))
(if (consp item) ;(LABEL . PROPS)
(car item)
item)))
(defun tablist-filter-op-equal (_id entry op1 op2)
"COLUMN == STRING : Matches if COLUMN's entry is equal to STRING."
(let ((item (tablist-filter-get-item-by-name entry op1)))
(string= item op2)))
(defun tablist-filter-op-regexp (_id entry op1 op2)
"COLUMN =~ REGEXP : Matches if COLUMN's entry matches REGEXP."
(let ((item (tablist-filter-get-item-by-name entry op1)))
(string-match op2 item)))
(defun tablist-filter-op-< (id entry op1 op2)
"COLUMN < NUMBER : Matches if COLUMN's entry is less than NUMBER."
(tablist-filter-op-numeric '< id entry op1 op2))
(defun tablist-filter-op-> (id entry op1 op2)
"COLUMN > NUMBER : Matches if COLUMN's entry is greater than NUMBER."
(tablist-filter-op-numeric '> id entry op1 op2))
(defun tablist-filter-op-<= (id entry op1 op2)
"COLUMN <= NUMBER : Matches if COLUMN's entry is less than or equal to NUMBER."
(tablist-filter-op-numeric '<= id entry op1 op2))
(defun tablist-filter-op->= (id entry op1 op2)
"COLUMN >= NUMBER : Matches if COLUMN's entry is greater than or equal to NUMBER."
(tablist-filter-op-numeric '>= id entry op1 op2))
(defun tablist-filter-op-= (id entry op1 op2)
"COLUMN = NUMBER : Matches if COLUMN's entry as a number is equal to NUMBER."
(tablist-filter-op-numeric '= id entry op1 op2))
(defun tablist-filter-op-numeric (op _id entry op1 op2)
(let ((item (tablist-filter-get-item-by-name entry op1)))
(funcall op (string-to-number item)
(string-to-number op2))))
(defun tablist-filter-help (&optional temporary)
(interactive)
(cl-labels
((princ-op (op)
(princ (car op))
(princ (concat (make-string (max 0 (- 4 (length (symbol-name (car op)))))
?\s)
"- "
(car (split-string
(or (documentation (cdr op))
(format "FIXME: Not documented: %s"
(cdr op)))
"\n" t))
"\n"))))
(with-temp-buffer-window
"*Help*"
(if temporary
'((lambda (buf alist)
(let ((win
(or (display-buffer-reuse-window buf alist)
(display-buffer-in-side-window buf alist))))
(fit-window-to-buffer win)
win))
(side . bottom)))
nil
(princ "Filter entries with the following operators.\n\n")
(princ "&& - FILTER1 && FILTER2 : Locical and.\n")
(princ "|| - FILTER1 || FILTER2 : Locical or.\n")
(dolist (op tablist-filter-binary-operator)
(princ-op op))
(princ "! - ! FILTER : Locical not.\n\n")
(dolist (op tablist-filter-unary-operator)
(princ-op op))
(princ "\"...\" may be used to quote names and values if necessary,
and \(...\) to group expressions.")
(with-current-buffer standard-output
(help-mode)))))
;;
;; *Filter Functions
;;
;; filter ::= nil | named | fn | (OP OP1 [OP2])
(defun tablist-filter-negate (filter)
"Return a filter not matching filter."
(cond
((eq (car-safe filter) 'not)
(cadr filter))
(filter
(list 'not filter))))
(defun tablist-filter-push (filter new-filter &optional or-p)
"Return a filter combining FILTER and NEW-FILTER.
By default the filters are and'ed, unless OR-P is non-nil."
(if (or (null filter)
(null new-filter))
(or filter
new-filter)
(list (if or-p 'or 'and)
filter new-filter)))
(defun tablist-filter-pop (filter)
"Remove the first operator or operand from filter.
If filter starts with a negation, return filter unnegated,
if filter starts with a dis- or conjunction, remove the first operand,
if filter is nil, raise an error,
else return nil."
(pcase filter
(`(,(or `and `or) . ,tail)
(car (cdr tail)))
(`(not . ,op1)
(car op1))
(_ (unless filter
(error "Filter is empty")))))
(defun tablist-filter-map (fn filter)
(pcase filter
(`(,(or `and `or `not) . ,tail)
(cons (car filter)
(mapcar (lambda (f)
(tablist-filter-map fn f))
tail)))
(_ (funcall fn filter))))
;;
;; *Reading Filter
;;
(defvar tablist-filter-edit-history nil)
(defvar tablist-filter-edit-display-help t)
(defun tablist-filter-edit-filter (prompt &optional
initial-filter history
validate-fn)
(let* ((str (tablist-filter-unparse initial-filter))
(filter initial-filter)
(validate-fn (or validate-fn 'identity))
error done)
(save-window-excursion
(when tablist-filter-edit-display-help
(tablist-filter-help t))
(while (not done)
(minibuffer-with-setup-hook
(lambda ()
(when error
(when (car error)
(goto-char (+ (field-beginning)
(car error)))
(skip-chars-backward " \t\n"))
(minibuffer-message "%s" (cdr error))
(setq error nil)))
(setq str (propertize
(read-string prompt str
(or history 'tablist-filter-edit-history)))
done t))
(condition-case err
(progn
(setq filter (tablist-filter-parse str))
(funcall validate-fn filter))
(error
(setq done nil)
(setq error (cons (car-safe (cddr err)) nil))
(when (car error)
(setq str (with-temp-buffer
(insert str)
(goto-char (car error))
(set-text-properties
(progn
(skip-chars-backward " \t\n")
(backward-char)
(point))
(min (car error) (point-max))
'(face error rear-nonsticky t))
(buffer-string))))
(setcdr error (error-message-string err)))))
filter)))
(provide 'tablist-filter)
;; Local Variables:
;; outline-regexp: ";;\\(\\(?:[;*]+ \\| \\*+\\)[^\s\t\n]\\|###autoload\\)\\|("
;; indent-tabs-mode: nil
;; End:
;;; tablist-filter.el ends here

View File

@ -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:

File diff suppressed because it is too large Load Diff

View File

@ -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

1
org/nov-places Normal file
View File

@ -0,0 +1 @@
((b872b0f0-02f2-479f-ba38-19a4549716b8 (index . 16) (point . 56303)))