├── .ert-runner ├── .gitignore ├── .travis.yml ├── COPYING ├── COPYING.SYNCTEX ├── Cask ├── Makefile ├── NEWS ├── README ├── README.org ├── TODO ├── appveyor.yml ├── ci └── appveyor │ ├── install.bat │ └── pack.bat ├── lisp ├── .gitignore ├── pdf-annot.el ├── pdf-cache.el ├── pdf-dev.el ├── pdf-history.el ├── pdf-info.el ├── pdf-isearch.el ├── pdf-links.el ├── pdf-loader.el ├── pdf-misc.el ├── pdf-occur.el ├── pdf-outline.el ├── pdf-sync.el ├── pdf-tools.el ├── pdf-util.el ├── pdf-view.el └── pdf-virtual.el ├── server ├── .gitignore ├── Makefile.am ├── autobuild ├── autogen.sh ├── configure.ac ├── epdfinfo.c ├── epdfinfo.h ├── m4 │ └── ax_check_compile_flag.m4 ├── poppler-hack.cc ├── poppler-versions ├── synctex_parser.c ├── synctex_parser.h ├── synctex_parser_advanced.h ├── synctex_parser_local.h ├── synctex_parser_readme.txt ├── synctex_parser_utils.c ├── synctex_parser_utils.h ├── synctex_parser_version.txt ├── synctex_version.h └── test │ ├── Makefile │ └── docker │ ├── .gitignore │ ├── lib │ ├── run-tests │ └── yes-or-enter │ └── templates │ ├── Dockerfile.in │ ├── arch.Dockerfile.in │ ├── centos-7.Dockerfile.in │ ├── debian-8.Dockerfile.in │ ├── debian-9.Dockerfile.in │ ├── fedora-24.Dockerfile.in │ ├── fedora-25.Dockerfile.in │ ├── fedora-26.Dockerfile.in │ ├── gentoo.Dockerfile.in │ ├── ubuntu-14.Dockerfile.in │ ├── ubuntu-16.Dockerfile.in │ └── ubuntu-17.Dockerfile.in └── test ├── encrypted.pdf ├── pdf-cache-test.el ├── pdf-info-test.el ├── pdf-loader-test.el ├── pdf-sync-test.el ├── pdf-tools-test.el ├── pdf-util-test.el ├── pdf-view-test.el ├── pdf-virtual-test.el ├── test-helper.el ├── test.pdf └── test.tex /.ert-runner: -------------------------------------------------------------------------------- 1 | --reporter ert+duration 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | aux/ 2 | dist/ 3 | .cask/ 4 | pdf-tools-*.tar 5 | pdf-tools-*/ 6 | pdf-tools-readme.txt 7 | pdf-tools-1.0.entry 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: generic 2 | sudo: false 3 | 4 | script: 5 | - emacs --version 6 | - make test 7 | 8 | env: 9 | matrix: 10 | - EVM_EMACS=emacs-24.3-travis 11 | - EVM_EMACS=emacs-24.4-travis 12 | - EVM_EMACS=emacs-24.5-travis 13 | - EVM_EMACS=emacs-git-snapshot-travis 14 | 15 | before_install: 16 | - curl -fsSkL https://gist.github.com/rejeep/ebcd57c3af83b049833b/raw > x.sh && source ./x.sh || true 17 | - sed -i 's/"https:/"http:/' ~/.cask/*.el 18 | - cask upgrade-cask || true 19 | - evm install $EVM_EMACS --use --skip 20 | - cask 21 | 22 | addons: 23 | apt: 24 | packages: 25 | - gcc 26 | - g++ 27 | - make 28 | - automake 29 | - autoconf 30 | - libpng-dev 31 | - libz-dev 32 | - libpoppler-glib-dev 33 | - libpoppler-private-dev 34 | 35 | notifications: 36 | email: false 37 | -------------------------------------------------------------------------------- /COPYING.SYNCTEX: -------------------------------------------------------------------------------- 1 | License: 2 | -------- 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE 23 | 24 | Except as contained in this notice, the name of the copyright holder 25 | shall not be used in advertising or otherwise to promote the sale, 26 | use or other dealings in this Software without prior written 27 | authorization from the copyright holder. 28 | -------------------------------------------------------------------------------- /Cask: -------------------------------------------------------------------------------- 1 | (source gnu) 2 | (source melpa) 3 | 4 | (package-file "lisp/pdf-tools.el") 5 | 6 | (files "lisp/*.el" 7 | "README" 8 | "server/epdfinfo" 9 | "server/epdfinfo.exe") 10 | 11 | (development 12 | (depends-on "let-alist") 13 | (depends-on "tablist") 14 | (depends-on "ert-runner") 15 | (depends-on "undercover")) 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | EMACS ?= emacs 3 | # Handle the mess when inside Emacs. 4 | unexport INSIDE_EMACS #cask not like this. 5 | ifeq ($(EMACS), t) 6 | EMACS = emacs 7 | endif 8 | 9 | emacs = $(EMACS) 10 | emacs_version = $(shell $(emacs) --batch --eval \ 11 | '(princ (format "%s.%s" emacs-major-version emacs-minor-version))') 12 | $(info Using Emacs $(emacs_version)) 13 | 14 | version=$(shell sed -ne 's/^;\+ *Version: *\([0-9.]\)/\1/p' lisp/pdf-tools.el) 15 | pkgname=pdf-tools-$(version) 16 | pkgfile=$(pkgname).tar 17 | 18 | .PHONY: all clean distclean bytecompile test check melpa cask-install 19 | 20 | all: $(pkgfile) 21 | 22 | # Create a elpa package including the server 23 | $(pkgfile): .cask/$(emacs_version) server/epdfinfo lisp/*.el 24 | cask package . 25 | 26 | # Compile the Lisp sources 27 | bytecompile: .cask/$(emacs_version) 28 | cask exec $(emacs) --batch -L lisp -f batch-byte-compile lisp/*.el 29 | 30 | # Run ERT tests 31 | test: all 32 | PACKAGE_TAR=$(pkgfile) cask exec ert-runner 33 | 34 | check: test 35 | 36 | # Run the autobuild script tests in docker 37 | test-autobuild: server-test 38 | 39 | # Run all tests 40 | test-all: test test-autobuild 41 | 42 | # Init cask 43 | .cask/$(emacs_version): 44 | cask install 45 | 46 | # Run the autobuild script (installing depends and compiling) 47 | autobuild: 48 | cd server && ./autobuild 49 | 50 | # Soon to be obsolete targets 51 | melpa-build: autobuild 52 | cp build/epdfinfo . 53 | install-server-deps: ; 54 | 55 | # Create a package like melpa would. 56 | melpa-package: $(pkgfile) 57 | cp $(pkgfile) $(pkgname)-melpa.tar 58 | tar -u --transform='s/server/$(pkgname)\/build\/server/' \ 59 | -f $(pkgname)-melpa.tar \ 60 | $$(git ls-files server) 61 | tar -u --transform='s/Makefile/$(pkgname)\/build\/Makefile/' \ 62 | -f $(pkgname)-melpa.tar \ 63 | Makefile 64 | tar -u --transform='s/README\.org/$(pkgname)\/README/' \ 65 | -f $(pkgname)-melpa.tar \ 66 | README.org 67 | -tar --delete $(pkgname)/epdfinfo \ 68 | -f $(pkgname)-melpa.tar 69 | 70 | # Various clean targets 71 | clean: server-clean 72 | rm -f -- $(pkgfile) 73 | rm -f -- lisp/*.elc 74 | rm -f -- pdf-tools-readme.txt 75 | rm -f -- pdf-tools-$(version).entry 76 | 77 | distclean: clean server-distclean 78 | rm -rf -- .cask 79 | 80 | # Server targets 81 | server/epdfinfo: server/Makefile server/*.[ch] 82 | $(MAKE) -C server 83 | 84 | server/Makefile: server/configure 85 | cd server && ./configure -q 86 | 87 | server/configure: server/configure.ac 88 | cd server && ./autogen.sh 89 | 90 | server-test: server/Makefile 91 | $(MAKE) -C server check 92 | 93 | server-clean: 94 | ! [ -f server/Makefile ] || $(MAKE) -C server clean 95 | 96 | server-distclean: 97 | ! [ -f server/Makefile ] || $(MAKE) -C server distclean 98 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | -*- org -*- 2 | 3 | * Version 1.0 4 | ** Added experimental support for high-resolution displays. 5 | Set `pdf-view-use-scaling' to a non-nil value in order to enable, 6 | if the appropriate image-formats and monitor are available. 7 | * Version 0.90 8 | ** The displayed columns when listing annotations is now customizable 9 | See variable pdf-annot-list-format and 10 | pdf-annot-list-highlight-type. 11 | ** Improved handling of default annotation properties 12 | A new variable pdf-annot-default-annotation-properties was 13 | introduced, subsuming and obsoleting 14 | pdf-annot-default-text-annotation-properties and 15 | pdf-annot-default-markup-annotation-properties. The new variable 16 | let's the user choose default properties, e.g. a color, for all 17 | supported annotations separately. 18 | ** Provide a faster "boot-loader" 19 | The autoloaded function pdf-loader-install acts as a replacement 20 | for pdf-tools-install and makes Emacs load and use PDF Tools as 21 | soon as a PDF file is opened, but not sooner. 22 | ** Improved the process of (re)compiling the server 23 | This obsoletes the variable pdf-tools-handle-upgrades, which does 24 | nothing anymore. 25 | * Version 0.80 26 | ** Tablist package 27 | The files tablist.el and tablist-filter.el are no longer part of 28 | pdf-tools, but continue to live on in the tablist package, on which 29 | pdf-tools now depends on. 30 | ** View 31 | *** Encrypted files 32 | When encountering an encrypted file, query for a password and 33 | attempt to decrypt it. 34 | *** Backward sync from isearch 35 | In isearch, press M-s s to visit the source of the current match. 36 | *** Disable unicode in mode-line 37 | New variable pdf-view-use-unicode-lighter which allows for 38 | disabling the use of unicode in the mode-line. 39 | * Version 0.70 40 | ** View 41 | *** Register integration 42 | The keys m and ' now set resp. jump to a register corresponding to 43 | a position in the PDF. Also '' is handled special: It jumps to the 44 | position before the last register-jump. 45 | *** Export parts of a page as an image 46 | ** Info 47 | *** Interface changes 48 | The return value of many pdf-info-* functions was changed in order 49 | to prefer alists over other data-structures (indexed lists, 50 | trees). 51 | ** Virtual PDF 52 | A virtual PDF is a collection of pages (or parts thereof) of 53 | arbitrary documents, which appear to the rest of pdf-tools as one 54 | big PDF, though they are always read-only. 55 | 56 | * Version 0.60 57 | ** Regexp support 58 | You may now search for perl-compatible regular expressions (PCRE) 59 | in PDF documents, both via Isearch and Occur. If that scares you, 60 | customize the variable pdf-occur-prefer-string-search. 61 | ** Occur 62 | *** Asynchronous search 63 | Searching is performed asynchronously in a private server 64 | instance, i.e. it doesn't block neither ordinary editing nor 65 | pdf-view-mode. 66 | *** Moccur 67 | Added the ability to search multiple documents in one occur 68 | buffer. 69 | ** Isearch 70 | *** Occur Integration 71 | M-s o now starts occur, while keeping the isearch session, like it 72 | is in text-buffers. 73 | *** Word search 74 | M-s w now does a word search, which will also find hyphenated 75 | words (as determined by pdf-isearch-hyphenation-character), though 76 | not at page boundaries. 77 | ** View 78 | *** Navigate by pagelabels 79 | M-g l may be used to jump to a page by label, i.e. it's displayed 80 | number. 81 | *** Rendering 82 | Added the ability to display the page as it would be printed 83 | (e.g. w/o annotations) and to apply a color filter 84 | (pdf-view-printer-minor-mode resp. pdf-view-midnight-minor-mode). 85 | ** Outline 86 | New option `pdf-outline-display-labels', determining whether to 87 | display labels instead of plain page-numbers. 88 | * Version 0.50 89 | ** PDF Tools is now available on MELPA. 90 | ** SyncTeX 91 | *** File name handling 92 | SyncTeX is pretty picky about source filenames. So instead of 93 | trying various filenames and hoping for best, we find it by 94 | directly inspecting the database. 95 | *** Heuristic backward search 96 | Backward searching now tries to find the exact position in the 97 | LaTeX buffer. This may be disabled by setting 98 | pdf-sync-backward-use-heuristic to nil. 99 | *** Renamed most variables/functions/commands. 100 | The old ones are still there but declared obsolete. 101 | ** Compatible with Emacs 24.3 102 | ** Integrate with bookmark.el 103 | ** Compiling on OSX 104 | PDF Tools should now compile on OSX, though it is unsupported. 105 | ** MELPA 106 | Try to handle an update via MELPA by package.el by shutting down 107 | the server, recompiling and restarting it. This may be deactivated 108 | by setting pdf-tools-handle-upgrades to nil. 109 | ** Auto slicing 110 | A new minor mode which will automatically slice the page according 111 | to it's bounding box. 112 | * Version 0.40 113 | I basically reimplemented the whole thing. (Not really, but a lot 114 | has changed.) 115 | ** Displaying PDF Files. 116 | Rendering is now done almost completely in libpoppler (no convert 117 | anymore), while PNG images are kept in memory and files are solely 118 | used as a means of exchange between Emacs and epdfinfo. In 119 | essence, display should be much faster. 120 | *** New Major Mode pdf-view 121 | Hacking up doc-view.el to support a server-based ,,rendering 122 | engine'' would have been to awkward. So a new major-mode was 123 | needed : pdf-view-mode . Both are very similar regarding 124 | user-interface. Some differences are: 125 | + Setting the width to `fit-width', `fit-height' or `fit-page' 126 | keeps up with window-size changes. 127 | + The values of the slice are relative, i.e. independent of the 128 | image-size. 129 | *** PNG Image Cache 130 | Image data is cached, in order to keep the time it needs to 131 | display a page low. Some pages are pre-loaded for the same reason, 132 | while idling. The number of cached images per buffer may be 133 | customized using `pdf-cache-image-limit'. 134 | ** Annotations 135 | *** New supported types 136 | Provided epdfinfo was build with a recent version of libpoppler, 137 | you may now create and modify the following markup annotation 138 | types: highlight, squiggly, underline and strike-out. 139 | ** Various 140 | *** You may now select extended regions with C-mouse-1. 141 | *** Numerous other changes 142 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | README.org -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | #+TITLE: PDF Tools README 2 | #+AUTHOR: Andreas Politz 3 | #+EMAIL: politza@fh-trier.de 4 | 5 | PDF Tools is dead, long live PDF Tools. Development continues with this fork: https://github.com/vedang/pdf-tools . 6 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | -*- org -*- 2 | 3 | * pdf-isearch 4 | ** Allow for entering multi-byte characters with some input-methods. 5 | The PDF buffer is in uni-byte mode prohibiting the user from 6 | inserting multi-byte characters in the minibuffer with some 7 | input-methods, while editing the search string. 8 | * PDF Forms 9 | Recent poppler versions have some support for editing forms. 10 | * pdf-annot 11 | ** Updating the list buffer is too slow 12 | + Update it incrementally. 13 | + Possibly skip the update if the buffer is not visible. 14 | ** Make highlighting customizable 15 | * epdfinfo 16 | ** Maybe split the code up in several files. 17 | * pdf-view 18 | ** Provide some kind of multi-page view 19 | ** Make persistent scrolling relative 20 | Currently the scrolling is kept when changing the image's size (in 21 | pdf-view-display-image), which is actually not so desirable, since 22 | it is absolute. This results e.g. in the image popping out of the 23 | window, when it is shrunken. 24 | * pdf-info 25 | ** Add a report/debug command, displaying a list of open files and other information. 26 | ** Use alists for results instead of positional lists. 27 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.0.{build} 2 | 3 | environment: 4 | matrix: 5 | - COMPILER: msys2 6 | PLATFORM: x64 7 | MSYS2_ARCH: x86_64 8 | MSYS2_DIR: msys64 9 | MSYSTEM: MINGW64 10 | BIT: 64 11 | 12 | install: 13 | # running under CI 14 | - '%APPVEYOR_BUILD_FOLDER%\ci\appveyor\install.bat' 15 | - 'echo End install at: & time /t' 16 | 17 | build_script: 18 | - 'pushd %APPVEYOR_BUILD_FOLDER%' 19 | - 'make autobuild -f %APPVEYOR_BUILD_FOLDER%\Makefile' 20 | 21 | after_build: 22 | - '%APPVEYOR_BUILD_FOLDER%\ci\appveyor\pack.bat' 23 | 24 | artifacts: 25 | - path: '*.zip' 26 | - path: '*.tar' 27 | -------------------------------------------------------------------------------- /ci/appveyor/install.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | cd %APPVEYOR_BUILD_FOLDER% 4 | 5 | echo Compiler: %COMPILER% 6 | echo Architecture: %MSYS2_ARCH% 7 | echo Platform: %PLATFORM% 8 | echo MSYS2 directory: %MSYS2_DIR% 9 | echo MSYS2 system: %MSYSTEM% 10 | echo Bits: %BIT% 11 | 12 | REM Create a writeable TMPDIR 13 | mkdir %APPVEYOR_BUILD_FOLDER%\tmp 14 | set TMPDIR=%APPVEYOR_BUILD_FOLDER%\tmp 15 | 16 | IF %COMPILER%==msys2 ( 17 | @echo on 18 | SET "PATH=C:\%MSYS2_DIR%\%MSYSTEM%\bin;C:\%MSYS2_DIR%\usr\bin;C:\%MSYS2_DIR%\home\appveyor\.cask\bin;%PATH%" 19 | 20 | bash -lc "pacman -S --needed --noconfirm git" 21 | 22 | REM dependencies 23 | bash -lc "pacman -S --needed --noconfirm mingw-w64-x86_64-zlib mingw-w64-x86_64-libpng mingw-w64-x86_64-poppler mingw-w64-x86_64-imagemagick openssl mingw-w64-x86_64-openssl" 24 | 25 | REM Set up emacs 26 | bash -lc "pacman -S --needed --noconfirm mingw-w64-x86_64-emacs" 27 | 28 | REM Set up Cask 29 | bash -lc "curl -fsSL https://raw.githubusercontent.com/cask/cask/master/go | python" 30 | ) 31 | -------------------------------------------------------------------------------- /ci/appveyor/pack.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | cd %APPVEYOR_BUILD_FOLDER% 4 | 5 | REM Create a writeable TMPDIR 6 | mkdir %APPVEYOR_BUILD_FOLDER%\pack 7 | set PACKDIR=%APPVEYOR_BUILD_FOLDER%\pack 8 | 9 | IF %COMPILER%==msys2 ( 10 | @echo on 11 | SET "PATH=C:\%MSYS2_DIR%\%MSYSTEM%\bin;C:\%MSYS2_DIR%\usr\bin;C:\%MSYS2_DIR%\home\appveyor\.cask\bin;%PATH%" 12 | 13 | REM Copy epdfinfo.exe and all dependencies 14 | bash -lc "pushd /c/projects/pdf-tools; ldd server/epdfinfo.exe | grep mingw | cut -d' ' -f 3 | xargs -I {} cp {} ./pack/; cp server/epdfinfo.exe ./pack/; cp /mingw64/bin/*eay32.dll ./pack/" 15 | 16 | REM Package epdfinfo.exe and all dependencies 17 | 7z a epdfinfo.zip %PACKDIR%\*.* 18 | ) 19 | -------------------------------------------------------------------------------- /lisp/.gitignore: -------------------------------------------------------------------------------- 1 | *.elc 2 | .dir-locals.el 3 | -------------------------------------------------------------------------------- /lisp/pdf-cache.el: -------------------------------------------------------------------------------- 1 | ;;; pdf-cache.el --- Cache time-critical or frequent epdfinfo queries. -*- lexical-binding:t -*- 2 | 3 | ;; Copyright (C) 2013 Andreas Politz 4 | 5 | ;; Author: Andreas Politz 6 | ;; Keywords: files, doc-view, pdf 7 | 8 | ;; This program is free software; you can redistribute it and/or modify 9 | ;; it under the terms of the GNU General Public License as published by 10 | ;; the Free Software Foundation, either version 3 of the License, or 11 | ;; (at your option) any later version. 12 | 13 | ;; This program is distributed in the hope that it will be useful, 14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | ;; GNU General Public License for more details. 17 | 18 | ;; You should have received a copy of the GNU General Public License 19 | ;; along with this program. If not, see . 20 | 21 | ;;; Commentary: 22 | ;; 23 | ;;; Code: 24 | ;; 25 | 26 | (require 'pdf-info) 27 | (require 'pdf-util) 28 | 29 | 30 | ;; * ================================================================== * 31 | ;; * Customiazations 32 | ;; * ================================================================== * 33 | 34 | (defcustom pdf-cache-image-limit 64 35 | "Maximum number of cached PNG images per buffer." 36 | :type 'integer 37 | :group 'pdf-cache 38 | :group 'pdf-view) 39 | 40 | (defcustom pdf-cache-prefetch-delay 0.5 41 | "Idle time in seconds before prefetching images starts." 42 | :group 'pdf-view 43 | :type 'number) 44 | 45 | (defcustom pdf-cache-prefetch-pages-function 46 | 'pdf-cache-prefetch-pages-function-default 47 | "A function returning a list of pages to be prefetched. 48 | 49 | It is called with no arguments in the PDF window and should 50 | return a list of page-numbers, determining the pages that should 51 | be prefetched and their order." 52 | :group 'pdf-view 53 | :type 'function) 54 | 55 | 56 | ;; * ================================================================== * 57 | ;; * Simple Value cache 58 | ;; * ================================================================== * 59 | 60 | (defvar-local pdf-cache--data nil) 61 | 62 | (defvar pdf-annot-modified-functions) 63 | 64 | (defun pdf-cache--initialize () 65 | (unless pdf-cache--data 66 | (setq pdf-cache--data (make-hash-table)) 67 | (add-hook 'pdf-info-close-document-hook 'pdf-cache-clear-data nil t) 68 | (add-hook 'pdf-annot-modified-functions 69 | 'pdf-cache--clear-data-of-annotations 70 | nil t))) 71 | 72 | (defun pdf-cache--clear-data-of-annotations (fn) 73 | (apply 'pdf-cache-clear-data-of-pages 74 | (mapcar (lambda (a) 75 | (cdr (assq 'page a))) 76 | (funcall fn t)))) 77 | 78 | (defun pdf-cache--data-put (key value &optional page) 79 | "Put KEY with VALUE in the cache of PAGE, return value." 80 | (pdf-cache--initialize) 81 | (puthash page (cons (cons key value) 82 | (assq-delete-all 83 | key 84 | (gethash page pdf-cache--data))) 85 | pdf-cache--data) 86 | value) 87 | 88 | (defun pdf-cache--data-get (key &optional page) 89 | "Get value of KEY in the cache of PAGE. 90 | 91 | Returns a cons \(HIT . VALUE\), where HIT is non-nil if KEY was 92 | stored previously for PAGE and VALUE it's value. Otherwise HIT 93 | is nil and VALUE undefined." 94 | (pdf-cache--initialize) 95 | (let ((elt (assq key (gethash page pdf-cache--data)))) 96 | (if elt 97 | (cons t (cdr elt)) 98 | (cons nil nil)))) 99 | 100 | (defun pdf-cache--data-clear (key &optional page) 101 | (pdf-cache--initialize) 102 | (puthash page 103 | (assq-delete-all key (gethash page pdf-cache--data)) 104 | pdf-cache--data) 105 | nil) 106 | 107 | (defun pdf-cache-clear-data-of-pages (&rest pages) 108 | (when pdf-cache--data 109 | (dolist (page pages) 110 | (remhash page pdf-cache--data)))) 111 | 112 | (defun pdf-cache-clear-data () 113 | (interactive) 114 | (when pdf-cache--data 115 | (clrhash pdf-cache--data))) 116 | 117 | (defmacro define-pdf-cache-function (command &optional page-arg-p) 118 | "Define a simple data cache function. 119 | 120 | COMMAND is the name of the command, e.g. number-of-pages. It 121 | should have a corresponding pdf-info function. If PAGE-ARG-P is 122 | non-nil, define a one-dimensional cache indexed by the page 123 | number. Otherwise the value is constant for each document, like 124 | e.g. number-of-pages. 125 | 126 | Both args are unevaluated." 127 | 128 | (let ((args (if page-arg-p (list 'page))) 129 | (fn (intern (format "pdf-cache-%s" command))) 130 | (ifn (intern (format "pdf-info-%s" command))) 131 | (doc (format "Cached version of `pdf-info-%s', which see. 132 | 133 | Make sure, not to modify it's return value." command))) 134 | `(defun ,fn ,args 135 | ,doc 136 | (let ((hit-value (pdf-cache--data-get ',command ,(if page-arg-p 'page)))) 137 | (if (car hit-value) 138 | (cdr hit-value) 139 | (pdf-cache--data-put 140 | ',command 141 | ,(if page-arg-p 142 | (list ifn 'page) 143 | (list ifn)) 144 | ,(if page-arg-p 'page))))))) 145 | 146 | (define-pdf-cache-function pagelinks t) 147 | (define-pdf-cache-function number-of-pages) 148 | ;; The boundingbox may change if annotations change. 149 | (define-pdf-cache-function boundingbox t) 150 | (define-pdf-cache-function textregions t) 151 | (define-pdf-cache-function pagesize t) 152 | 153 | 154 | ;; * ================================================================== * 155 | ;; * PNG image LRU cache 156 | ;; * ================================================================== * 157 | 158 | (defvar pdf-cache-image-inihibit nil 159 | "Non-nil, if the image cache should be bypassed.") 160 | 161 | (defvar-local pdf-cache--image-cache nil) 162 | 163 | (defmacro pdf-cache--make-image (page width data hash) 164 | `(list ,page ,width ,data ,hash)) 165 | (defmacro pdf-cache--image/page (img) `(nth 0 ,img)) 166 | (defmacro pdf-cache--image/width (img) `(nth 1 ,img)) 167 | (defmacro pdf-cache--image/data (img) `(nth 2 ,img)) 168 | (defmacro pdf-cache--image/hash (img) `(nth 3 ,img)) 169 | 170 | (defun pdf-cache--image-match (image page min-width &optional max-width hash) 171 | "Match IMAGE with specs. 172 | 173 | IMAGE should be a list as created by `pdf-cache--make-image'. 174 | 175 | Return non-nil, if IMAGE's page is the same as PAGE, it's width 176 | is at least MIN-WIDTH and at most MAX-WIDTH and it's stored 177 | hash-value is `eql' to HASH." 178 | (and (= (pdf-cache--image/page image) 179 | page) 180 | (or (null min-width) 181 | (>= (pdf-cache--image/width image) 182 | min-width)) 183 | (or (null max-width) 184 | (<= (pdf-cache--image/width image) 185 | max-width)) 186 | (eql (pdf-cache--image/hash image) 187 | hash))) 188 | 189 | (defun pdf-cache-lookup-image (page min-width &optional max-width hash) 190 | "Return PAGE's cached PNG data as a string or nil. 191 | 192 | Does not modify the cache. See also `pdf-cache-get-image'." 193 | (let ((image (car (cl-member 194 | (list page min-width max-width hash) 195 | pdf-cache--image-cache 196 | :test (lambda (spec image) 197 | (apply 'pdf-cache--image-match image spec)))))) 198 | (and image 199 | (pdf-cache--image/data image)))) 200 | 201 | (defun pdf-cache-get-image (page min-width &optional max-width hash) 202 | "Return PAGE's PNG data as a string. 203 | 204 | Return an image of at least MIN-WIDTH and, if non-nil, maximum 205 | width MAX-WIDTH and `eql' hash value. 206 | 207 | Remember that image was recently used. 208 | 209 | Returns nil, if no matching image was found." 210 | (let ((cache pdf-cache--image-cache) 211 | image) 212 | ;; Find it in the cache. 213 | (while (and (setq image (pop cache)) 214 | (not (pdf-cache--image-match 215 | image page min-width max-width hash)))) 216 | ;; Remove it and push it to the front. 217 | (when image 218 | (setq pdf-cache--image-cache 219 | (cons image (delq image pdf-cache--image-cache))) 220 | (pdf-cache--image/data image)))) 221 | 222 | (defun pdf-cache-put-image (page width data &optional hash) 223 | "Cache image of PAGE with WIDTH, DATA and HASH. 224 | 225 | DATA should the string of a PNG image of width WIDTH and from 226 | page PAGE in the current buffer. See `pdf-cache-get-image' for 227 | the HASH argument. 228 | 229 | This function always returns nil." 230 | (unless pdf-cache--image-cache 231 | (add-hook 'pdf-info-close-document-hook 'pdf-cache-clear-images nil t) 232 | (add-hook 'pdf-annot-modified-functions 233 | 'pdf-cache--clear-images-of-annotations nil t)) 234 | (push (pdf-cache--make-image page width data hash) 235 | pdf-cache--image-cache) 236 | ;; Forget old image(s). 237 | (when (> (length pdf-cache--image-cache) 238 | pdf-cache-image-limit) 239 | (if (> pdf-cache-image-limit 1) 240 | (setcdr (nthcdr (1- pdf-cache-image-limit) 241 | pdf-cache--image-cache) 242 | nil) 243 | (setq pdf-cache--image-cache nil))) 244 | nil) 245 | 246 | (defun pdf-cache-clear-images () 247 | "Clear the image cache." 248 | (setq pdf-cache--image-cache nil)) 249 | 250 | (defun pdf-cache-clear-images-if (fn) 251 | "Remove images from the cache according to FN. 252 | 253 | FN should be function accepting 4 Arguments \(PAGE WIDTH DATA 254 | HASH\). It should return non-nil, if the image should be removed 255 | from the cache." 256 | (setq pdf-cache--image-cache 257 | (cl-remove-if 258 | (lambda (image) 259 | (funcall 260 | fn 261 | (pdf-cache--image/page image) 262 | (pdf-cache--image/width image) 263 | (pdf-cache--image/data image) 264 | (pdf-cache--image/hash image))) 265 | pdf-cache--image-cache))) 266 | 267 | 268 | (defun pdf-cache--clear-images-of-annotations (fn) 269 | (apply 'pdf-cache-clear-images-of-pages 270 | (mapcar (lambda (a) 271 | (cdr (assq 'page a))) 272 | (funcall fn t)))) 273 | 274 | (defun pdf-cache-clear-images-of-pages (&rest pages) 275 | (pdf-cache-clear-images-if 276 | (lambda (page &rest _) (memq page pages)))) 277 | 278 | (defun pdf-cache-renderpage (page min-width &optional max-width) 279 | "Render PAGE according to MIN-WIDTH and MAX-WIDTH. 280 | 281 | Return the PNG data of an image as a string, such that it's width 282 | is at least MIN-WIDTH and, if non-nil, at most MAX-WIDTH. 283 | 284 | If such an image is not available in the cache, call 285 | `pdf-info-renderpage' to create one." 286 | (if pdf-cache-image-inihibit 287 | (pdf-info-renderpage page min-width) 288 | (or (pdf-cache-get-image page min-width max-width) 289 | (let ((data (pdf-info-renderpage page min-width))) 290 | (pdf-cache-put-image page min-width data) 291 | data)))) 292 | 293 | (defun pdf-cache-renderpage-text-regions (page width single-line-p 294 | &rest selection) 295 | "Render PAGE according to WIDTH, SINGLE-LINE-P and SELECTION. 296 | 297 | See also `pdf-info-renderpage-text-regions' and 298 | `pdf-cache-renderpage'." 299 | (if pdf-cache-image-inihibit 300 | (apply 'pdf-info-renderpage-text-regions 301 | page width single-line-p nil selection) 302 | (let ((hash (sxhash 303 | (format "%S" (cons 'renderpage-text-regions 304 | (cons single-line-p selection)))))) 305 | (or (pdf-cache-get-image page width width hash) 306 | (let ((data (apply 'pdf-info-renderpage-text-regions 307 | page width single-line-p nil selection))) 308 | (pdf-cache-put-image page width data hash) 309 | data))))) 310 | 311 | (defun pdf-cache-renderpage-highlight (page width &rest regions) 312 | "Highlight PAGE according to WIDTH and REGIONS. 313 | 314 | See also `pdf-info-renderpage-highlight' and 315 | `pdf-cache-renderpage'." 316 | (if pdf-cache-image-inihibit 317 | (apply 'pdf-info-renderpage-highlight 318 | page width nil regions) 319 | (let ((hash (sxhash 320 | (format "%S" (cons 'renderpage-highlight 321 | regions))))) 322 | (or (pdf-cache-get-image page width width hash) 323 | (let ((data (apply 'pdf-info-renderpage-highlight 324 | page width nil regions))) 325 | (pdf-cache-put-image page width data hash) 326 | data))))) 327 | 328 | 329 | ;; * ================================================================== * 330 | ;; * Prefetching images 331 | ;; * ================================================================== * 332 | 333 | (defvar-local pdf-cache--prefetch-pages nil 334 | "Pages to be prefetched.") 335 | 336 | (defvar-local pdf-cache--prefetch-timer nil 337 | "Timer used when prefetching images.") 338 | 339 | (define-minor-mode pdf-cache-prefetch-minor-mode 340 | "Try to load images which will probably be needed in a while." 341 | nil nil nil 342 | (pdf-cache--prefetch-cancel) 343 | (cond 344 | (pdf-cache-prefetch-minor-mode 345 | (pdf-util-assert-pdf-buffer) 346 | (add-hook 'pre-command-hook 'pdf-cache--prefetch-stop nil t) 347 | ;; FIXME: Disable the time when the buffer is killed or it's 348 | ;; major-mode changes. 349 | (setq pdf-cache--prefetch-timer 350 | (run-with-idle-timer (or pdf-cache-prefetch-delay 1) 351 | t 'pdf-cache--prefetch-start (current-buffer)))) 352 | (t 353 | (remove-hook 'pre-command-hook 'pdf-cache--prefetch-stop t)))) 354 | 355 | (defun pdf-cache-prefetch-pages-function-default () 356 | (let ((page (pdf-view-current-page))) 357 | (pdf-util-remove-duplicates 358 | (cl-remove-if-not 359 | (lambda (page) 360 | (and (>= page 1) 361 | (<= page (pdf-cache-number-of-pages)))) 362 | (append 363 | ;; +1, -1, +2, -2, ... 364 | (let ((sign 1) 365 | (incr 1)) 366 | (mapcar (lambda (_) 367 | (setq page (+ page (* sign incr)) 368 | sign (- sign) 369 | incr (1+ incr)) 370 | page) 371 | (number-sequence 1 16))) 372 | ;; First and last 373 | (list 1 (pdf-cache-number-of-pages)) 374 | ;; Links 375 | (mapcar 376 | (apply-partially 'alist-get 'page) 377 | (cl-remove-if-not 378 | (lambda (link) (eq (alist-get 'type link) 'goto-dest)) 379 | (pdf-cache-pagelinks 380 | (pdf-view-current-page))))))))) 381 | 382 | (defun pdf-cache--prefetch-pages (window image-width) 383 | (when (and (eq window (selected-window)) 384 | (pdf-util-pdf-buffer-p)) 385 | (let ((page (pop pdf-cache--prefetch-pages))) 386 | (while (and page 387 | (pdf-cache-lookup-image 388 | page 389 | image-width 390 | (if (not (pdf-view-use-scaling-p)) 391 | image-width 392 | (* 2 image-width)))) 393 | (setq page (pop pdf-cache--prefetch-pages))) 394 | (pdf-util-debug 395 | (when (null page) 396 | (message "Prefetching done."))) 397 | (when page 398 | (let* ((buffer (current-buffer)) 399 | (pdf-info-asynchronous 400 | (lambda (status data) 401 | (when (and (null status) 402 | (eq window 403 | (selected-window)) 404 | (eq buffer (window-buffer))) 405 | (with-current-buffer (window-buffer) 406 | (when (derived-mode-p 'pdf-view-mode) 407 | (pdf-cache-put-image 408 | page image-width data) 409 | (image-size (pdf-view-create-page page)) 410 | (pdf-util-debug 411 | (message "Prefetched page %s." page)) 412 | ;; Avoid max-lisp-eval-depth 413 | (run-with-timer 414 | 0.001 nil 'pdf-cache--prefetch-pages window image-width))))))) 415 | (condition-case err 416 | (pdf-info-renderpage page image-width) 417 | (error 418 | (pdf-cache-prefetch-minor-mode -1) 419 | (signal (car err) (cdr err))))))))) 420 | 421 | (defvar pdf-cache--prefetch-started-p nil 422 | "Guard against multiple prefetch starts. 423 | 424 | Used solely in `pdf-cache--prefetch-start'.") 425 | 426 | (defun pdf-cache--prefetch-start (buffer) 427 | "Start prefetching images in BUFFER." 428 | (when (and pdf-cache-prefetch-minor-mode 429 | (not pdf-cache--prefetch-started-p) 430 | (pdf-util-pdf-buffer-p) 431 | (not isearch-mode) 432 | (null pdf-cache--prefetch-pages) 433 | (eq (window-buffer) buffer) 434 | (fboundp pdf-cache-prefetch-pages-function)) 435 | (let* ((pdf-cache--prefetch-started-p t) 436 | (pages (funcall pdf-cache-prefetch-pages-function))) 437 | (setq pdf-cache--prefetch-pages 438 | (butlast pages (max 0 (- (length pages) 439 | pdf-cache-image-limit)))) 440 | (pdf-cache--prefetch-pages 441 | (selected-window) 442 | (car (pdf-view-desired-image-size)))))) 443 | 444 | (defun pdf-cache--prefetch-stop () 445 | "Stop prefetching images in current buffer." 446 | (setq pdf-cache--prefetch-pages nil)) 447 | 448 | (defun pdf-cache--prefetch-cancel () 449 | "Cancel prefetching images in current buffer." 450 | (pdf-cache--prefetch-stop) 451 | (when pdf-cache--prefetch-timer 452 | (cancel-timer pdf-cache--prefetch-timer)) 453 | (setq pdf-cache--prefetch-timer nil)) 454 | 455 | (provide 'pdf-cache) 456 | ;;; pdf-cache.el ends here 457 | -------------------------------------------------------------------------------- /lisp/pdf-dev.el: -------------------------------------------------------------------------------- 1 | ;;; pdf-dev.el --- Mother's little development helper -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2015 Andreas Politz 4 | 5 | ;; Author: Andreas Politz 6 | ;; Keywords: 7 | 8 | ;; This program is free software; you can redistribute it and/or modify 9 | ;; it under the terms of the GNU General Public License as published by 10 | ;; the Free Software Foundation, either version 3 of the License, or 11 | ;; (at your option) any later version. 12 | 13 | ;; This program is distributed in the hope that it will be useful, 14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | ;; GNU General Public License for more details. 17 | 18 | ;; You should have received a copy of the GNU General Public License 19 | ;; along with this program. If not, see . 20 | 21 | ;;; Commentary: 22 | ;; 23 | ;; This file is only meant for developers. The entry point is 24 | ;; pdf-dev-minor-mode, which see. 25 | 26 | ;;; Code: 27 | 28 | (defvar pdf-dev-root-directory 29 | (file-name-directory 30 | (directory-file-name 31 | (file-name-directory load-file-name)))) 32 | 33 | (defun pdf-dev-reload () 34 | "Reload lisp files from source." 35 | (interactive) 36 | (let ((default-directory (expand-file-name 37 | "lisp" 38 | pdf-dev-root-directory)) 39 | loaded) 40 | (dolist (file (directory-files default-directory nil "\\`pdf-\\w*\\.el\\'")) 41 | (push file loaded) 42 | (load-file file)) 43 | (message "Loaded %s" (mapconcat 'identity loaded " ")))) 44 | 45 | (define-minor-mode pdf-dev-minor-mode 46 | "Make developing pdf-tools easier. 47 | 48 | It does the following: 49 | 50 | Quits the server and sets `pdf-info-epdfinfo-program' to 51 | ../server/epdfinfo. 52 | 53 | Installs a `compilation-finish-functions' which will restart 54 | epdfinfo after a successful recompilation. 55 | 56 | Sets up `load-path' and reloads all PDF Tools lisp files." 57 | nil nil nil 58 | (let ((lisp-dir (expand-file-name "lisp" pdf-dev-root-directory))) 59 | (setq load-path (remove lisp-dir load-path)) 60 | (cond 61 | (pdf-dev-minor-mode 62 | (add-hook 'compilation-finish-functions 'pdf-dev-compilation-finished) 63 | (add-to-list 'load-path lisp-dir) 64 | (setq pdf-info-epdfinfo-program 65 | (expand-file-name 66 | "epdfinfo" 67 | (expand-file-name "server" pdf-dev-root-directory))) 68 | (pdf-info-quit) 69 | (pdf-dev-reload)) 70 | (t 71 | (remove-hook 'compilation-finish-functions 'pdf-dev-compilation-finished))))) 72 | 73 | (defun pdf-dev-compilation-finished (buffer status) 74 | (with-current-buffer buffer 75 | (when (and (equal status "finished\n") 76 | (file-equal-p 77 | (expand-file-name "server" pdf-dev-root-directory) 78 | default-directory)) 79 | (message "Restarting epdfinfo server") 80 | (pdf-info-quit) 81 | (let ((pdf-info-restart-process-p t)) 82 | (pdf-info-process-assert-running))))) 83 | 84 | (provide 'pdf-dev) 85 | ;;; pdf-dev.el ends here 86 | -------------------------------------------------------------------------------- /lisp/pdf-history.el: -------------------------------------------------------------------------------- 1 | ;;; pdf-history.el --- A simple stack-based history in PDF buffers. -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2013, 2014 Andreas Politz 4 | 5 | ;; Author: Andreas Politz 6 | ;; Keywords: files, multimedia 7 | 8 | ;; This program is free software; you can redistribute it and/or modify 9 | ;; it under the terms of the GNU General Public License as published by 10 | ;; the Free Software Foundation, either version 3 of the License, or 11 | ;; (at your option) any later version. 12 | 13 | ;; This program is distributed in the hope that it will be useful, 14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | ;; GNU General Public License for more details. 17 | 18 | ;; You should have received a copy of the GNU General Public License 19 | ;; along with this program. If not, see . 20 | 21 | ;;; Commentary: 22 | ;; 23 | 24 | (require 'pdf-view) 25 | (require 'pdf-util) 26 | 27 | ;;; Code: 28 | 29 | (defgroup pdf-history nil 30 | "A simple stack-based history." 31 | :group 'pdf-tools) 32 | 33 | (defvar-local pdf-history-stack nil 34 | "The stack of history items.") 35 | 36 | (defvar-local pdf-history-index nil 37 | "The current index into the `pdf-history-stack'.") 38 | 39 | (defvar pdf-history-minor-mode-map 40 | (let ((kmap (make-sparse-keymap))) 41 | (define-key kmap (kbd "B") 'pdf-history-backward) 42 | (define-key kmap (kbd "N") 'pdf-history-forward) 43 | kmap) 44 | "Keymap used in `pdf-history-minor-mode'.") 45 | 46 | ;;;###autoload 47 | (define-minor-mode pdf-history-minor-mode 48 | "Keep a history of previously visited pages. 49 | 50 | This is a simple stack-based history. Turning the page or 51 | following a link pushes the left-behind page on the stack, which 52 | may be navigated with the following keys. 53 | 54 | \\{pdf-history-minor-mode-map}" 55 | nil nil nil 56 | (pdf-util-assert-pdf-buffer) 57 | (pdf-history-clear) 58 | (cond 59 | (pdf-history-minor-mode 60 | (pdf-history-push) 61 | (add-hook 'pdf-view-after-change-page-hook 62 | 'pdf-history-before-change-page-hook nil t)) 63 | (t 64 | (remove-hook 'pdf-view-after-change-page-hook 65 | 'pdf-history-before-change-page-hook t)))) 66 | 67 | (defun pdf-history-before-change-page-hook () 68 | "Push a history item, before leaving this page." 69 | (when (and pdf-history-minor-mode 70 | (not (bound-and-true-p pdf-isearch-active-mode)) 71 | (pdf-view-current-page)) 72 | (pdf-history-push))) 73 | 74 | (defun pdf-history-push () 75 | "Push the current page on the stack. 76 | 77 | This function does nothing, if current stack item already 78 | represents the current page." 79 | (interactive) 80 | (let ((item (pdf-history-create-item))) 81 | (unless (and pdf-history-stack 82 | (equal (nth pdf-history-index 83 | pdf-history-stack) item)) 84 | (setq pdf-history-stack 85 | (last pdf-history-stack 86 | (- (length pdf-history-stack) 87 | pdf-history-index)) 88 | pdf-history-index 0) 89 | (push item pdf-history-stack)))) 90 | 91 | (defun pdf-history-clear () 92 | "Remove all history items." 93 | (interactive) 94 | (setq pdf-history-stack nil 95 | pdf-history-index 0) 96 | (pdf-history-push)) 97 | 98 | (defun pdf-history-create-item () 99 | "Create a history item representing the current page." 100 | (list 101 | (pdf-view-current-page))) 102 | 103 | (defun pdf-history-beginning-of-history-p () 104 | "Return t, if at the beginning of the history." 105 | (= pdf-history-index 0)) 106 | 107 | (defun pdf-history-end-of-history-p () 108 | "Return t, if at the end of the history." 109 | (= pdf-history-index 110 | (1- (length pdf-history-stack)))) 111 | 112 | (defun pdf-history-backward (n) 113 | "Go N-times backward in the history." 114 | (interactive "p") 115 | (cond 116 | ((and (> n 0) 117 | (pdf-history-end-of-history-p)) 118 | (error "End of history")) 119 | ((and (< n 0) 120 | (pdf-history-beginning-of-history-p)) 121 | (error "Beginning of history")) 122 | ((/= n 0) 123 | (let ((i (min (max 0 (+ pdf-history-index n)) 124 | (1- (length pdf-history-stack))))) 125 | (prog1 126 | (- (+ pdf-history-index n) i) 127 | (pdf-history-goto i)))) 128 | (t 0))) 129 | 130 | (defun pdf-history-forward (n) 131 | "Go N-times forward in the history." 132 | (interactive "p") 133 | (pdf-history-backward (- n))) 134 | 135 | (defun pdf-history-goto (n) 136 | "Go to item N in the history." 137 | (interactive "p") 138 | (when (null pdf-history-stack) 139 | (error "The history is empty")) 140 | (cond 141 | ((>= n (length pdf-history-stack)) 142 | (error "End of history")) 143 | ((< n 0) 144 | (error "Beginning of history")) 145 | (t 146 | (setq pdf-history-index n) 147 | (pdf-view-goto-page 148 | (car (nth n pdf-history-stack)))))) 149 | 150 | (defun pdf-history-debug () 151 | "Visualize the history in the header-line." 152 | (interactive) 153 | (setq header-line-format 154 | '(:eval 155 | (let ((pages (mapcar 'car pdf-history-stack)) 156 | (index pdf-history-index) 157 | header) 158 | (dotimes (i (length pages)) 159 | (push (propertize 160 | (format "%s" (nth i pages)) 161 | 'face 162 | (and (= i index) 'match)) 163 | header)) 164 | (concat 165 | "(" (format "%d" index) ") " 166 | (mapconcat 'identity (nreverse header) " | ")))))) 167 | 168 | (provide 'pdf-history) 169 | 170 | ;;; pdf-history.el ends here 171 | -------------------------------------------------------------------------------- /lisp/pdf-links.el: -------------------------------------------------------------------------------- 1 | ;;; pdf-links.el --- Handle PDF links. -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2013, 2014 Andreas Politz 4 | 5 | ;; Author: Andreas Politz 6 | ;; Keywords: files, multimedia 7 | 8 | ;; This program is free software; you can redistribute it and/or modify 9 | ;; it under the terms of the GNU General Public License as published by 10 | ;; the Free Software Foundation, either version 3 of the License, or 11 | ;; (at your option) any later version. 12 | 13 | ;; This program is distributed in the hope that it will be useful, 14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | ;; GNU General Public License for more details. 17 | 18 | ;; You should have received a copy of the GNU General Public License 19 | ;; along with this program. If not, see . 20 | 21 | ;;; Commentary: 22 | ;; 23 | 24 | (require 'pdf-info) 25 | (require 'pdf-util) 26 | (require 'pdf-misc) 27 | (require 'pdf-cache) 28 | (require 'pdf-isearch) 29 | (require 'let-alist) 30 | (require 'org) 31 | 32 | ;;; Code: 33 | 34 | 35 | 36 | ;; * ================================================================== * 37 | ;; * Customizations 38 | ;; * ================================================================== * 39 | 40 | (defgroup pdf-links nil 41 | "Following links in PDF documents." 42 | :group 'pdf-tools) 43 | 44 | (defface pdf-links-read-link 45 | '((((background dark)) (:background "red" :foreground "yellow")) 46 | (((background light)) (:background "red" :foreground "yellow"))) 47 | "Face used to determine the colors when reading links." 48 | ;; :group 'pdf-links 49 | :group 'pdf-tools-faces) 50 | 51 | (defcustom pdf-links-read-link-convert-commands 52 | '(;;"-font" "FreeMono" 53 | "-pointsize" "%P" 54 | "-undercolor" "%f" 55 | "-fill" "%b" 56 | "-draw" "text %X,%Y '%c'") 57 | 58 | "The commands for the convert program, when decorating links for reading. 59 | See `pdf-util-convert' for an explanation of the format. 60 | 61 | Aside from the description there, two additional escape chars are 62 | available. 63 | 64 | %P -- The scaled font pointsize, i.e. IMAGE-WIDTH * SCALE (See 65 | `pdf-links-convert-pointsize-scale'). 66 | %c -- String describing the current link key (e.g. AA, AB, 67 | etc.)." 68 | :group 'pdf-links 69 | :type '(repeat string) 70 | :link '(variable-link pdf-isearch-convert-commands) 71 | :link '(url-link "http://www.imagemagick.org/script/convert.php")) 72 | 73 | (defcustom pdf-links-convert-pointsize-scale 0.01 74 | "The scale factor for the -pointsize convert command. 75 | 76 | This determines the relative size of the font, when interactively 77 | reading links." 78 | :group 'pdf-links 79 | :type '(restricted-sexp :match-alternatives 80 | ((lambda (x) (and (numberp x) 81 | (<= x 1) 82 | (>= x 0)))))) 83 | 84 | (defcustom pdf-links-browse-uri-function 85 | 'pdf-links-browse-uri-default 86 | "The function for handling uri links. 87 | 88 | This function should accept one argument, the URI to follow, and 89 | do something with it." 90 | :group 'pdf-links 91 | :type 'function) 92 | 93 | 94 | ;; * ================================================================== * 95 | ;; * Minor Mode 96 | ;; * ================================================================== * 97 | 98 | (defvar pdf-links-minor-mode-map 99 | (let ((kmap (make-sparse-keymap))) 100 | (define-key kmap (kbd "f") 'pdf-links-isearch-link) 101 | (define-key kmap (kbd "F") 'pdf-links-action-perform) 102 | kmap)) 103 | 104 | ;;;###autoload 105 | (define-minor-mode pdf-links-minor-mode 106 | "Handle links in PDF documents.\\ 107 | 108 | If this mode is enabled, most links in the document may be 109 | activated by clicking on them or by pressing \\[pdf-links-action-perform] and selecting 110 | one of the displayed keys, or by using isearch limited to 111 | links via \\[pdf-links-isearch-link]. 112 | 113 | \\{pdf-links-minor-mode-map}" 114 | 115 | nil nil nil 116 | :group 'pdf-links 117 | (pdf-util-assert-pdf-buffer) 118 | (cond 119 | (pdf-links-minor-mode 120 | (pdf-view-add-hotspot-function 'pdf-links-hotspots-function 0)) 121 | (t 122 | (pdf-view-remove-hotspot-function 'pdf-links-hotspots-function))) 123 | (pdf-view-redisplay t)) 124 | 125 | (defun pdf-links-hotspots-function (page size) 126 | "Create hotspots for links on PAGE using SIZE." 127 | 128 | (let ((links (pdf-cache-pagelinks page)) 129 | (id-fmt "link-%d-%d") 130 | (i 0) 131 | (pointer 'hand) 132 | hotspots) 133 | (dolist (l links) 134 | (let ((e (pdf-util-scale 135 | (cdr (assq 'edges l)) size 'round)) 136 | (id (intern (format id-fmt page 137 | (cl-incf i))))) 138 | (push `((rect . ((,(nth 0 e) . ,(nth 1 e)) 139 | . (,(nth 2 e) . ,(nth 3 e)))) 140 | ,id 141 | (pointer 142 | ,pointer 143 | help-echo ,(pdf-links-action-to-string l))) 144 | hotspots) 145 | (local-set-key 146 | (vector id 'mouse-1) 147 | (lambda nil 148 | (interactive "@") 149 | (pdf-links-action-perform l))) 150 | (local-set-key 151 | (vector id t) 152 | 'pdf-util-image-map-mouse-event-proxy))) 153 | (nreverse hotspots))) 154 | 155 | (defun pdf-links-action-to-string (link) 156 | "Return a string representation of ACTION." 157 | (let-alist link 158 | (concat 159 | (cl-case .type 160 | (goto-dest 161 | (if (> .page 0) 162 | (format "Goto page %d" .page) 163 | "Destination not found")) 164 | (goto-remote 165 | (if (and .filename (file-exists-p .filename)) 166 | (format "Goto %sfile '%s'" 167 | (if (> .page 0) 168 | (format "p.%d of " .page) 169 | "") 170 | .filename) 171 | (format "Link to nonexistent file '%s'" .filename))) 172 | (uri 173 | (if (> (length .uri) 0) 174 | (format "Link to uri '%s'" .uri) 175 | (format "Link to empty uri"))) 176 | (t (format "Unrecognized link type: %s" .type))) 177 | (if (> (length .title) 0) 178 | (format " (%s)" .title))))) 179 | 180 | ;;;###autoload 181 | (defun pdf-links-action-perform (link) 182 | "Follow LINK, depending on its type. 183 | 184 | This may turn to another page, switch to another PDF buffer or 185 | invoke `pdf-links-browse-uri-function'. 186 | 187 | Interactively, link is read via `pdf-links-read-link-action'. 188 | This function displays characters around the links in the current 189 | page and starts reading characters (ignoring case). After a 190 | sufficient number of characters have been read, the corresponding 191 | link's link is invoked. Additionally, SPC may be used to 192 | scroll the current page." 193 | (interactive 194 | (list (or (pdf-links-read-link-action "Activate link (SPC scrolls): ") 195 | (error "No link selected")))) 196 | (let-alist link 197 | (cl-case .type 198 | ((goto-dest goto-remote) 199 | (let ((window (selected-window))) 200 | (cl-case .type 201 | (goto-dest 202 | (unless (> .page 0) 203 | (error "Link points to nowhere"))) 204 | (goto-remote 205 | (unless (and .filename (file-exists-p .filename)) 206 | (error "Link points to nonexistent file %s" .filename)) 207 | (setq window (display-buffer 208 | (or (find-buffer-visiting .filename) 209 | (find-file-noselect .filename)))))) 210 | (with-selected-window window 211 | (when (derived-mode-p 'pdf-view-mode) 212 | (when (> .page 0) 213 | (pdf-view-goto-page .page)) 214 | (when .top 215 | ;; Showing the tooltip delays displaying the page for 216 | ;; some reason (sit-for/redisplay don't help), do it 217 | ;; later. 218 | (run-with-idle-timer 0.001 nil 219 | (lambda () 220 | (when (window-live-p window) 221 | (with-selected-window window 222 | (when (derived-mode-p 'pdf-view-mode) 223 | (pdf-util-tooltip-arrow .top))))))))))) 224 | (uri 225 | (funcall pdf-links-browse-uri-function .uri)) 226 | (t 227 | (error "Unrecognized link type: %s" .type))) 228 | nil)) 229 | 230 | (defun pdf-links-read-link-action (prompt) 231 | "Using PROMPT, interactively read a link-action. 232 | 233 | See `pdf-links-action-perform' for the interface." 234 | 235 | (pdf-util-assert-pdf-window) 236 | (let* ((links (pdf-cache-pagelinks 237 | (pdf-view-current-page))) 238 | (keys (pdf-links-read-link-action--create-keys 239 | (length links))) 240 | (key-strings (mapcar (apply-partially 'apply 'string) 241 | keys)) 242 | (alist (cl-mapcar 'cons keys links)) 243 | (size (pdf-view-image-size)) 244 | (colors (pdf-util-face-colors 245 | 'pdf-links-read-link pdf-view-dark-minor-mode)) 246 | (args (list 247 | :foreground (car colors) 248 | :background (cdr colors) 249 | :formats 250 | `((?c . ,(lambda (_edges) (pop key-strings))) 251 | (?P . ,(number-to-string 252 | (max 1 (* (cdr size) 253 | pdf-links-convert-pointsize-scale))))) 254 | :commands pdf-links-read-link-convert-commands 255 | :apply (pdf-util-scale-relative-to-pixel 256 | (mapcar (lambda (l) (cdr (assq 'edges l))) 257 | links))))) 258 | (unless links 259 | (error "No links on this page")) 260 | (unwind-protect 261 | (let ((image-data 262 | (pdf-cache-get-image 263 | (pdf-view-current-page) 264 | (car size) (car size) 'pdf-links-read-link-action))) 265 | (unless image-data 266 | (setq image-data (apply 'pdf-util-convert-page args )) 267 | (pdf-cache-put-image 268 | (pdf-view-current-page) 269 | (car size) image-data 'pdf-links-read-link-action)) 270 | (pdf-view-display-image 271 | (create-image image-data (pdf-view-image-type) t)) 272 | (pdf-links-read-link-action--read-chars prompt alist)) 273 | (pdf-view-redisplay)))) 274 | 275 | (defun pdf-links-read-link-action--read-chars (prompt alist) 276 | (catch 'done 277 | (let (key) 278 | (while t 279 | (let* ((chars (append (mapcar 'caar alist) 280 | (mapcar 'downcase (mapcar 'caar alist)) 281 | (list ?\s))) 282 | (ch (read-char-choice prompt chars))) 283 | (setq ch (upcase ch)) 284 | (cond 285 | ((= ch ?\s) 286 | (when (= (window-vscroll) (image-scroll-up)) 287 | (image-scroll-down (window-vscroll)))) 288 | (t 289 | (setq alist (delq nil (mapcar (lambda (elt) 290 | (and (eq ch (caar elt)) 291 | (cons (cdar elt) 292 | (cdr elt)))) 293 | alist)) 294 | key (append key (list ch)) 295 | prompt (concat prompt (list ch))) 296 | (when (= (length alist) 1) 297 | (message nil) 298 | (throw 'done (cdar alist)))))))))) 299 | 300 | (defun pdf-links-read-link-action--create-keys (n) 301 | (when (> n 0) 302 | (let ((len (1+ (floor (log n 26)))) 303 | keys) 304 | (dotimes (i n) 305 | (let (key) 306 | (dotimes (_x len) 307 | (push (+ (% i 26) ?A) key) 308 | (setq i (/ i 26))) 309 | (push key keys))) 310 | (nreverse keys)))) 311 | 312 | (defun pdf-links-isearch-link () 313 | (interactive) 314 | (let* (quit-p 315 | (isearch-mode-end-hook 316 | (cons (lambda nil 317 | (setq quit-p isearch-mode-end-hook-quit)) 318 | isearch-mode-end-hook)) 319 | (pdf-isearch-filter-matches-function 320 | 'pdf-links-isearch-link-filter-matches) 321 | (pdf-isearch-narrow-to-page t) 322 | (isearch-message-prefix-add "(Links)") 323 | pdf-isearch-batch-mode) 324 | (isearch-forward) 325 | (unless (or quit-p (null pdf-isearch-current-match)) 326 | (let* ((page (pdf-view-current-page)) 327 | (match (car pdf-isearch-current-match)) 328 | (size (pdf-view-image-size)) 329 | (links (sort (cl-remove-if 330 | (lambda (e) 331 | (= 0 (pdf-util-edges-intersection-area (car e) match))) 332 | (mapcar (lambda (l) 333 | (cons (pdf-util-scale (alist-get 'edges l) size) 334 | l)) 335 | (pdf-cache-pagelinks page))) 336 | (lambda (e1 e2) 337 | (> (pdf-util-edges-intersection-area 338 | (alist-get 'edges e1) match) 339 | (pdf-util-edges-intersection-area 340 | (alist-get 'edges e2) match)))))) 341 | (unless links 342 | (error "No link found at this position")) 343 | (pdf-links-action-perform (car links)))))) 344 | 345 | (defun pdf-links-isearch-link-filter-matches (matches) 346 | (let ((links (pdf-util-scale 347 | (mapcar (apply-partially 'alist-get 'edges) 348 | (pdf-cache-pagelinks 349 | (pdf-view-current-page))) 350 | (pdf-view-image-size)))) 351 | (cl-remove-if-not 352 | (lambda (m) 353 | (cl-some 354 | (lambda (edges) 355 | (cl-some (lambda (link) 356 | (pdf-util-with-edges (link edges) 357 | (let ((area (min (* link-width link-height) 358 | (* edges-width edges-height)))) 359 | (> (/ (pdf-util-edges-intersection-area edges link) 360 | (float area)) 0.5)))) 361 | links)) 362 | m)) 363 | matches))) 364 | 365 | (defun pdf-links-browse-uri-default (uri) 366 | "Open the string URI using Org. 367 | 368 | Wraps the URI in \[\[ ... \]\] and calls `org-open-link-from-string' 369 | on the resulting string." 370 | (cl-check-type uri string) 371 | (message "Opening `%s' with Org" uri) 372 | (org-open-link-from-string (format "[[%s]]" uri))) 373 | 374 | (provide 'pdf-links) 375 | 376 | ;;; pdf-links.el ends here 377 | -------------------------------------------------------------------------------- /lisp/pdf-loader.el: -------------------------------------------------------------------------------- 1 | ;;; pdf-loader.el --- Minimal PDF Tools loader -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2017 Andreas Politz 4 | 5 | ;; Author: Andreas Politz 6 | ;; Keywords: 7 | 8 | ;; This program is free software; you can redistribute it and/or modify 9 | ;; it under the terms of the GNU General Public License as published by 10 | ;; the Free Software Foundation, either version 3 of the License, or 11 | ;; (at your option) any later version. 12 | 13 | ;; This program is distributed in the hope that it will be useful, 14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | ;; GNU General Public License for more details. 17 | 18 | ;; You should have received a copy of the GNU General Public License 19 | ;; along with this program. If not, see . 20 | 21 | ;;; Commentary: 22 | 23 | ;; 24 | 25 | ;;; Code: 26 | 27 | (defconst pdf-loader--auto-mode-alist-item 28 | (copy-sequence "\\.[pP][dD][fF]\\'") 29 | "The item used in `auto-mode-alist'.") 30 | 31 | (defconst pdf-loader--magic-mode-alist-item 32 | (copy-sequence "%PDF") 33 | "The item used in`magic-mode-alist'.") 34 | 35 | 36 | (declare-function pdf-tools-install "pdf-tools.el") 37 | 38 | ;;;###autoload 39 | (defun pdf-loader-install (&optional no-query-p skip-dependencies-p 40 | no-error-p force-dependencies-p) 41 | "Prepare Emacs for using PDF Tools. 42 | 43 | This function acts as a replacement for `pdf-tools-install' and 44 | makes Emacs load and use PDF Tools as soon as a PDF file is 45 | opened, but not sooner. 46 | 47 | The arguments are passed verbatim to `pdf-tools-install', which 48 | see." 49 | (let ((args (list no-query-p skip-dependencies-p 50 | no-error-p force-dependencies-p))) 51 | (if (featurep 'pdf-tools) 52 | (apply #'pdf-tools-install args) 53 | (pdf-loader--install 54 | (lambda () 55 | (apply #'pdf-loader--load args)))))) 56 | 57 | (defun pdf-loader--load (&rest args) 58 | (pdf-loader--uninstall) 59 | (save-selected-window 60 | (pdf-tools-install args))) 61 | 62 | (defun pdf-loader--install (loader) 63 | (pdf-loader--uninstall) 64 | (push (cons pdf-loader--auto-mode-alist-item loader) 65 | auto-mode-alist) 66 | (push (cons pdf-loader--magic-mode-alist-item loader) 67 | magic-mode-alist)) 68 | 69 | (defun pdf-loader--uninstall () 70 | (let ((elt (assoc pdf-loader--auto-mode-alist-item 71 | auto-mode-alist))) 72 | (when elt 73 | (setq auto-mode-alist (remove elt auto-mode-alist)))) 74 | (let ((elt (assoc pdf-loader--magic-mode-alist-item 75 | magic-mode-alist))) 76 | (when elt 77 | (setq magic-mode-alist (remove elt magic-mode-alist))))) 78 | 79 | (provide 'pdf-loader) 80 | ;;; pdf-loader.el ends here 81 | -------------------------------------------------------------------------------- /lisp/pdf-misc.el: -------------------------------------------------------------------------------- 1 | ;;; pdf-misc.el --- Miscellaneous commands for PDF buffer. 2 | 3 | ;; Copyright (C) 2013, 2014 Andreas Politz 4 | 5 | ;; Author: Andreas Politz 6 | ;; Keywords: files, multimedia 7 | 8 | ;; This program is free software; you can redistribute it and/or modify 9 | ;; it under the terms of the GNU General Public License as published by 10 | ;; the Free Software Foundation, either version 3 of the License, or 11 | ;; (at your option) any later version. 12 | 13 | ;; This program is distributed in the hope that it will be useful, 14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | ;; GNU General Public License for more details. 17 | 18 | ;; You should have received a copy of the GNU General Public License 19 | ;; along with this program. If not, see . 20 | 21 | ;;; Commentary: 22 | ;; 23 | 24 | 25 | (require 'pdf-view) 26 | (require 'pdf-util) 27 | (require 'imenu) 28 | 29 | 30 | 31 | (defvar pdf-misc-minor-mode-map 32 | (let ((map (make-sparse-keymap))) 33 | (define-key map (kbd "I") 'pdf-misc-display-metadata) 34 | (define-key map (kbd "C-c C-p") 'pdf-misc-print-document) 35 | map) 36 | "Keymap used in `pdf-misc-minor-mode'.") 37 | 38 | ;;;###autoload 39 | (define-minor-mode pdf-misc-minor-mode 40 | "FIXME: Not documented." 41 | nil nil nil) 42 | 43 | ;;;###autoload 44 | (define-minor-mode pdf-misc-size-indication-minor-mode 45 | "Provide a working size indication in the mode-line." 46 | nil nil nil 47 | (pdf-util-assert-pdf-buffer) 48 | (cond 49 | (pdf-misc-size-indication-minor-mode 50 | (unless (assq 'pdf-misc-size-indication-minor-mode 51 | mode-line-position) 52 | (setq mode-line-position 53 | `((pdf-misc-size-indication-minor-mode 54 | (:eval (pdf-misc-size-indication))) 55 | ,@mode-line-position)))) 56 | (t 57 | (setq mode-line-position 58 | (cl-remove 'pdf-misc-size-indication-minor-mode 59 | mode-line-position :key 'car-safe))))) 60 | 61 | (defun pdf-misc-size-indication () 62 | "Return size indication string for the mode-line." 63 | (let ((top (= (window-vscroll nil t) 0)) 64 | (bot (>= (+ (- (nth 3 (window-inside-pixel-edges)) 65 | (nth 1 (window-inside-pixel-edges))) 66 | (window-vscroll nil t)) 67 | (cdr (pdf-view-image-size t))))) 68 | (cond 69 | ((and top bot) " All") 70 | (top " Top") 71 | (bot " Bot") 72 | (t (format 73 | " %d%%%%" 74 | (ceiling 75 | (* 100 (/ (float (window-vscroll nil t)) 76 | (cdr (pdf-view-image-size t)))))))))) 77 | 78 | (defvar pdf-misc-menu-bar-minor-mode-map (make-sparse-keymap) 79 | "The keymap used in `pdf-misc-menu-bar-minor-mode'.") 80 | 81 | (easy-menu-define nil pdf-misc-menu-bar-minor-mode-map 82 | "Menu for PDF Tools." 83 | `("PDF Tools" 84 | ["Go Backward" pdf-history-backward 85 | :visible (bound-and-true-p pdf-history-minor-mode) 86 | :active (and (bound-and-true-p pdf-history-minor-mode) 87 | (not (pdf-history-end-of-history-p)))] 88 | ["Go Forward" pdf-history-forward 89 | :visible (bound-and-true-p pdf-history-minor-mode) 90 | :active (not (pdf-history-end-of-history-p))] 91 | ["--" nil 92 | :visible (derived-mode-p 'pdf-virtual-view-mode)] 93 | ["Next file" pdf-virtual-buffer-forward-file 94 | :visible (derived-mode-p 'pdf-virtual-view-mode) 95 | :active (pdf-virtual-document-next-file 96 | (pdf-view-current-page))] 97 | ["Previous file" pdf-virtual-buffer-backward-file 98 | :visible (derived-mode-p 'pdf-virtual-view-mode) 99 | :active (not (eq 1 (pdf-view-current-page)))] 100 | ["--" nil 101 | :visible (bound-and-true-p pdf-history-minor-mode)] 102 | ["Add text annotation" pdf-annot-mouse-add-text-annotation 103 | :visible (bound-and-true-p pdf-annot-minor-mode) 104 | :keys "\\[pdf-annot-add-text-annotation]"] 105 | ("Add markup annotation" 106 | :active (pdf-view-active-region-p) 107 | :visible (and (bound-and-true-p pdf-annot-minor-mode) 108 | (pdf-info-markup-annotations-p)) 109 | ["highlight" pdf-annot-add-highlight-markup-annotation] 110 | ["squiggly" pdf-annot-add-squiggly-markup-annotation] 111 | ["underline" pdf-annot-add-underline-markup-annotation] 112 | ["strikeout" pdf-annot-add-strikeout-markup-annotation]) 113 | ["--" nil :visible (bound-and-true-p pdf-annot-minor-mode)] 114 | ["Display Annotations" pdf-annot-list-annotations 115 | :help "List all annotations" 116 | :visible (bound-and-true-p pdf-annot-minor-mode)] 117 | ["Display Attachments" pdf-annot-attachment-dired 118 | :help "Display attachments in a dired buffer" 119 | :visible (featurep 'pdf-annot)] 120 | ["Display Metadata" pdf-misc-display-metadata 121 | :help "Display information about the document" 122 | :visible (featurep 'pdf-misc)] 123 | ["Display Outline" pdf-outline 124 | :help "Display documents outline" 125 | :visible (featurep 'pdf-outline)] 126 | "--" 127 | ("Render Options" 128 | ["Printed Mode" (lambda () 129 | (interactive) 130 | (pdf-view-printer-minor-mode 'toggle)) 131 | :style toggle 132 | :selected pdf-view-printer-minor-mode 133 | :help "Display the PDF as it would be printed."] 134 | ["Midnight Mode" (lambda () 135 | (interactive) 136 | (pdf-view-midnight-minor-mode 'toggle)) 137 | :style toggle 138 | :selected pdf-view-midnight-minor-mode 139 | :help "Apply a color-filter appropriate for past midnight reading."]) 140 | "--" 141 | ["Copy region" pdf-view-kill-ring-save 142 | :keys "\\[kill-ring-save]" 143 | :active (pdf-view-active-region-p)] 144 | "--" 145 | ["Isearch document" isearch-forward 146 | :visible (bound-and-true-p pdf-isearch-minor-mode)] 147 | ["Occur document" pdf-occur 148 | :visible (featurep 'pdf-occur)] 149 | "--" 150 | ["Locate TeX source" pdf-sync-backward-search-mouse 151 | :visible (and (featurep 'pdf-sync) 152 | (equal last-command-event 153 | last-nonmenu-event))] 154 | ["--" nil :visible (and (featurep 'pdf-sync) 155 | (equal last-command-event 156 | last-nonmenu-event))] 157 | ["Print" pdf-misc-print-document 158 | :active (and (pdf-view-buffer-file-name) 159 | (file-readable-p (pdf-view-buffer-file-name)))] 160 | ["Create image" pdf-view-extract-region-image 161 | :help "Create an image of the page or the selected region(s)."] 162 | ["Create virtual PDF" pdf-virtual-buffer-create 163 | :help "Create a PDF containing all documents in this directory." 164 | :visible (bound-and-true-p pdf-virtual-global-minor-mode)] 165 | "--" 166 | ["Revert buffer" pdf-view-revert-buffer 167 | :visible (pdf-info-writable-annotations-p)] 168 | "--" 169 | ["Customize" pdf-tools-customize])) 170 | 171 | ;;;###autoload 172 | (define-minor-mode pdf-misc-menu-bar-minor-mode 173 | "Display a PDF Tools menu in the menu-bar." 174 | nil nil nil 175 | (pdf-util-assert-pdf-buffer)) 176 | 177 | (defvar pdf-misc-context-menu-minor-mode-map 178 | (let ((kmap (make-sparse-keymap))) 179 | (define-key kmap [down-mouse-3] 'pdf-misc-popup-context-menu) 180 | kmap)) 181 | 182 | ;;;###autoload 183 | (define-minor-mode pdf-misc-context-menu-minor-mode 184 | "Provide a right-click context menu in PDF buffers. 185 | 186 | \\{pdf-misc-context-menu-minor-mode-map}" 187 | nil nil nil 188 | (pdf-util-assert-pdf-buffer)) 189 | 190 | (defun pdf-misc-popup-context-menu (event) 191 | "Popup a context menu at position determined by EVENT." 192 | (interactive "@e") 193 | (popup-menu 194 | (cons 'keymap 195 | (cddr (lookup-key pdf-misc-menu-bar-minor-mode-map 196 | [menu-bar PDF\ Tools]))))) 197 | 198 | (defun pdf-misc-display-metadata () 199 | "Display all available metadata in a separate buffer." 200 | (interactive) 201 | (pdf-util-assert-pdf-buffer) 202 | (let* ((buffer (current-buffer)) 203 | (md (pdf-info-metadata))) 204 | (with-current-buffer (get-buffer-create "*PDF-Metadata*") 205 | (let* ((inhibit-read-only t) 206 | (pad (apply' max (mapcar (lambda (d) 207 | (length (symbol-name (car d)))) 208 | md))) 209 | (fmt (format "%%%ds:%%s\n" pad)) 210 | window) 211 | (erase-buffer) 212 | (setq header-line-format (buffer-name buffer) 213 | buffer-read-only t) 214 | (font-lock-mode 1) 215 | (font-lock-add-keywords nil 216 | '(("^ *\\(\\(?:\\w\\|-\\)+\\):" 217 | (1 font-lock-keyword-face)))) 218 | (dolist (d md) 219 | (let ((key (car d)) 220 | (val (cdr d))) 221 | (cl-case key 222 | (keywords 223 | (setq val (mapconcat 'identity val ", ")))) 224 | (let ((beg (+ (length (symbol-name key)) (point) 1)) 225 | (fill-prefix 226 | (make-string (1+ pad) ?\s))) 227 | (insert (format fmt key val)) 228 | (fill-region beg (point) ))))) 229 | (goto-char 1) 230 | (display-buffer (current-buffer))) 231 | md)) 232 | 233 | (defgroup pdf-misc nil 234 | "Miscellaneous options for PDF documents." 235 | :group 'pdf-tools) 236 | 237 | (define-obsolete-variable-alias 'pdf-misc-print-programm 238 | 'pdf-misc-print-program "1.0") 239 | (defcustom pdf-misc-print-programm nil 240 | "The program used for printing. 241 | 242 | It is called with one argument, the PDF file." 243 | :group 'pdf-misc 244 | :type 'file) 245 | 246 | (define-obsolete-variable-alias 'pdf-misc-print-programm-args 247 | 'pdf-misc-print-program-args "1.0") 248 | (defcustom pdf-misc-print-programm-args nil 249 | "List of additional arguments passed to `pdf-misc-print-program'." 250 | :group 'pdf-misc 251 | :type '(repeat string)) 252 | 253 | (defun pdf-misc-print-programm (&optional interactive-p) 254 | (or (and pdf-misc-print-programm 255 | (executable-find pdf-misc-print-programm)) 256 | (when interactive-p 257 | (let* ((default (car (delq nil (mapcar 258 | 'executable-find 259 | '("gtklp" "xpp" "gpr"))))) 260 | buffer-file-name 261 | (program 262 | (expand-file-name 263 | (read-file-name 264 | "Print with: " default nil t nil 'file-executable-p)))) 265 | (when (and program 266 | (executable-find program)) 267 | (when (y-or-n-p "Save choice using customize ?") 268 | (customize-save-variable 269 | 'pdf-misc-print-program program)) 270 | (setq pdf-misc-print-program program)))))) 271 | 272 | (defun pdf-misc-print-document (filename &optional interactive-p) 273 | (interactive 274 | (list (pdf-view-buffer-file-name) t)) 275 | (cl-check-type filename (and string file-readable)) 276 | (let ((program (pdf-misc-print-program interactive-p)) 277 | (args (append pdf-misc-print-programm-args (list filename)))) 278 | (unless program 279 | (error "No print program available")) 280 | (apply #'start-process "printing" nil program args) 281 | (message "Print job started: %s %s" 282 | program (mapconcat #'identity args " ")))) 283 | 284 | 285 | (provide 'pdf-misc) 286 | 287 | ;;; pdf-misc.el ends here 288 | -------------------------------------------------------------------------------- /lisp/pdf-tools.el: -------------------------------------------------------------------------------- 1 | ;;; pdf-tools.el --- Support library for PDF documents. -*- lexical-binding:t -*- 2 | 3 | ;; Copyright (C) 2013, 2014 Andreas Politz 4 | 5 | ;; Author: Andreas Politz 6 | ;; Keywords: files, multimedia 7 | ;; Package: pdf-tools 8 | ;; Version: 1.0 9 | ;; Package-Requires: ((emacs "24.3") (tablist "1.0") (let-alist "1.0.4")) 10 | 11 | ;; This program is free software; you can redistribute it and/or modify 12 | ;; it under the terms of the GNU General Public License as published by 13 | ;; the Free Software Foundation, either version 3 of the License, or 14 | ;; (at your option) any later version. 15 | 16 | ;; This program is distributed in the hope that it will be useful, 17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | ;; GNU General Public License for more details. 20 | 21 | ;; You should have received a copy of the GNU General Public License 22 | ;; along with this program. If not, see . 23 | 24 | ;;; Commentary: 25 | ;; 26 | ;; PDF Tools is, among other things, a replacement of DocView for PDF 27 | ;; files. The key difference is, that pages are not prerendered by 28 | ;; e.g. ghostscript and stored in the file-system, but rather created 29 | ;; on-demand and stored in memory. 30 | ;; 31 | ;; Note: This package requires external libraries and works currently 32 | ;; only on GNU/Linux systems. 33 | ;; 34 | ;; Note: If you ever update it, you need to restart Emacs afterwards. 35 | ;; 36 | ;; To activate the package put 37 | ;; 38 | ;; (pdf-tools-install) 39 | ;; 40 | ;; somewhere in your .emacs.el . 41 | ;; 42 | ;; M-x pdf-tools-help RET 43 | ;; 44 | ;; gives some help on using the package and 45 | ;; 46 | ;; M-x pdf-tools-customize RET 47 | ;; 48 | ;; offers some customization options. 49 | 50 | ;; Features: 51 | ;; 52 | ;; * View 53 | ;; View PDF documents in a buffer with DocView-like bindings. 54 | ;; 55 | ;; * Isearch 56 | ;; Interactively search PDF documents like any other buffer. (Though 57 | ;; there is currently no regexp support.) 58 | ;; 59 | ;; * Follow links 60 | ;; Click on highlighted links, moving to some part of a different 61 | ;; page, some external file, a website or any other URI. Links may 62 | ;; also be followed by keyboard commands. 63 | ;; 64 | ;; * Annotations 65 | ;; Display and list text and markup annotations (like underline), 66 | ;; edit their contents and attributes (e.g. color), move them around, 67 | ;; delete them or create new ones and then save the modifications 68 | ;; back to the PDF file. 69 | ;; 70 | ;; * Attachments 71 | ;; Save files attached to the PDF-file or list them in a dired buffer. 72 | ;; 73 | ;; * Outline 74 | ;; Use imenu or a special buffer to examine and navigate the PDF's 75 | ;; outline. 76 | ;; 77 | ;; * SyncTeX 78 | ;; Jump from a position on a page directly to the TeX source and 79 | ;; vice-versa. 80 | ;; 81 | ;; * Misc 82 | ;; + Display PDF's metadata. 83 | ;; + Mark a region and kill the text from the PDF. 84 | ;; + Search for occurrences of a string. 85 | ;; + Keep track of visited pages via a history. 86 | 87 | ;;; Code: 88 | 89 | (require 'pdf-view) 90 | (require 'pdf-util) 91 | (require 'pdf-info) 92 | (require 'cus-edit) 93 | (require 'compile) 94 | (require 'cl-lib) 95 | (require 'package) 96 | 97 | 98 | 99 | ;; * ================================================================== * 100 | ;; * Customizables 101 | ;; * ================================================================== * 102 | 103 | (defgroup pdf-tools nil 104 | "Support library for PDF documents." 105 | :group 'data) 106 | 107 | (defgroup pdf-tools-faces nil 108 | "Faces determining the colors used in the pdf-tools package. 109 | 110 | In order to customize dark and light colors use 111 | `pdf-tools-customize-faces', or set `custom-face-default-form' to 112 | 'all." 113 | :group 'pdf-tools) 114 | 115 | (defconst pdf-tools-modes 116 | '(pdf-history-minor-mode 117 | pdf-isearch-minor-mode 118 | pdf-links-minor-mode 119 | pdf-misc-minor-mode 120 | pdf-outline-minor-mode 121 | pdf-misc-size-indication-minor-mode 122 | pdf-misc-menu-bar-minor-mode 123 | pdf-annot-minor-mode 124 | pdf-sync-minor-mode 125 | pdf-misc-context-menu-minor-mode 126 | pdf-cache-prefetch-minor-mode 127 | pdf-view-auto-slice-minor-mode 128 | pdf-occur-global-minor-mode 129 | pdf-virtual-global-minor-mode)) 130 | 131 | (defcustom pdf-tools-enabled-modes 132 | '(pdf-history-minor-mode 133 | pdf-isearch-minor-mode 134 | pdf-links-minor-mode 135 | pdf-misc-minor-mode 136 | pdf-outline-minor-mode 137 | pdf-misc-size-indication-minor-mode 138 | pdf-misc-menu-bar-minor-mode 139 | pdf-annot-minor-mode 140 | pdf-sync-minor-mode 141 | pdf-misc-context-menu-minor-mode 142 | pdf-cache-prefetch-minor-mode 143 | pdf-occur-global-minor-mode 144 | ;; pdf-virtual-global-minor-mode 145 | ) 146 | "A list of automatically enabled minor-modes. 147 | 148 | PDF Tools is build as a series of minor-modes. This variable and 149 | the function `pdf-tools-install' merely serve as a convenient 150 | wrapper in order to load these modes in current and newly created 151 | PDF buffers." 152 | :group 'pdf-tools 153 | :type `(set ,@(mapcar (lambda (mode) 154 | `(function-item ,mode)) 155 | pdf-tools-modes))) 156 | 157 | (defcustom pdf-tools-enabled-hook nil 158 | "A hook ran after PDF Tools is enabled in a buffer." 159 | :group 'pdf-tools 160 | :type 'hook) 161 | 162 | (defconst pdf-tools-auto-mode-alist-entry 163 | '("\\.[pP][dD][fF]\\'" . pdf-view-mode) 164 | "The entry to use for `auto-mode-alist'.") 165 | 166 | (defconst pdf-tools-magic-mode-alist-entry 167 | '("%PDF" . pdf-view-mode) 168 | "The entry to use for `magic-mode-alist'.") 169 | 170 | (defun pdf-tools-customize () 171 | "Customize Pdf Tools." 172 | (interactive) 173 | (customize-group 'pdf-tools)) 174 | 175 | (defun pdf-tools-customize-faces () 176 | "Customize PDF Tool's faces." 177 | (interactive) 178 | (let ((buffer (format "*Customize Group: %s*" 179 | (custom-unlispify-tag-name 'pdf-tools-faces)))) 180 | (when (buffer-live-p (get-buffer buffer)) 181 | (with-current-buffer (get-buffer buffer) 182 | (rename-uniquely))) 183 | (customize-group 'pdf-tools-faces) 184 | (with-current-buffer buffer 185 | (set (make-local-variable 'custom-face-default-form) 'all)))) 186 | 187 | 188 | ;; * ================================================================== * 189 | ;; * Installation 190 | ;; * ================================================================== * 191 | 192 | ;;;###autoload 193 | (defcustom pdf-tools-handle-upgrades t 194 | "Whether PDF Tools should handle upgrading itself." 195 | :group 'pdf-tools 196 | :type 'boolean) 197 | 198 | (make-obsolete-variable 'pdf-tools-handle-upgrades 199 | "Not used anymore" "0.90") 200 | 201 | (defconst pdf-tools-directory 202 | (or (and load-file-name 203 | (file-name-directory load-file-name)) 204 | default-directory) 205 | "The directory from where this library was first loaded.") 206 | 207 | (defvar pdf-tools-msys2-directory nil) 208 | 209 | (defcustom pdf-tools-installer-os nil 210 | "Specifies which installer to use. 211 | 212 | If nil the installer is chosen automatically. This variable is 213 | useful if you have multiple installers present on your 214 | system (e.g. nix on arch linux)" 215 | :group 'pdf-tools 216 | :type 'string) 217 | 218 | (defun pdf-tools-identify-build-directory (directory) 219 | "Return non-nil, if DIRECTORY appears to contain the epdfinfo source. 220 | 221 | Returns the expanded directory-name of DIRECTORY or nil." 222 | (setq directory (file-name-as-directory 223 | (expand-file-name directory))) 224 | (and (file-exists-p (expand-file-name "autobuild" directory)) 225 | (file-exists-p (expand-file-name "epdfinfo.c" directory)) 226 | directory)) 227 | 228 | (defun pdf-tools-locate-build-directory () 229 | "Attempt to locate a source directory. 230 | 231 | Returns a appropriate directory or nil. See also 232 | `pdf-tools-identify-build-directory'." 233 | (cl-some #'pdf-tools-identify-build-directory 234 | (list default-directory 235 | (expand-file-name "build/server" pdf-tools-directory) 236 | (expand-file-name "server") 237 | (expand-file-name "../server" pdf-tools-directory)))) 238 | 239 | (defun pdf-tools-msys2-directory (&optional noninteractive-p) 240 | "Locate the Msys2 installation directory. 241 | 242 | Ask the user if necessary and NONINTERACTIVE-P is nil. 243 | Returns always nil, unless `system-type' equals windows-nt." 244 | (cl-labels ((if-msys2-directory (directory) 245 | (and (stringp directory) 246 | (file-directory-p directory) 247 | (file-exists-p 248 | (expand-file-name "usr/bin/bash.exe" directory)) 249 | directory))) 250 | (when (eq system-type 'windows-nt) 251 | (setq pdf-tools-msys2-directory 252 | (or pdf-tools-msys2-directory 253 | (cl-some #'if-msys2-directory 254 | (cl-mapcan 255 | (lambda (drive) 256 | (list (format "%c:/msys64" drive) 257 | (format "%c:/msys32" drive))) 258 | (number-sequence ?c ?z))) 259 | (unless (or noninteractive-p 260 | (not (y-or-n-p "Do you have Msys2 installed ? "))) 261 | (if-msys2-directory 262 | (read-directory-name 263 | "Please enter Msys2 installation directory: " nil nil t)))))))) 264 | 265 | (defun pdf-tools-msys2-mingw-bin () 266 | "Return the location of /mingw*/bin." 267 | (when (pdf-tools-msys2-directory) 268 | (let ((arch (intern (car (split-string system-configuration "-" t))))) 269 | (expand-file-name 270 | (format "./mingw%s/bin" (if (eq arch 'x86_64) "64" "32")) 271 | (pdf-tools-msys2-directory))))) 272 | 273 | (defun pdf-tools-find-bourne-shell () 274 | "Locate a usable sh." 275 | (or (and (eq system-type 'windows-nt) 276 | (let* ((directory (pdf-tools-msys2-directory))) 277 | (when directory 278 | (expand-file-name "usr/bin/bash.exe" directory)))) 279 | (executable-find "sh"))) 280 | 281 | (defun pdf-tools-build-server (target-directory 282 | &optional 283 | skip-dependencies-p 284 | force-dependencies-p 285 | callback 286 | build-directory) 287 | "Build the epdfinfo program in the background. 288 | 289 | Install into TARGET-DIRECTORY, which should be a directory. 290 | 291 | If CALLBACK is non-nil, it should be a function. It is called 292 | with the compiled executable as the single argument or nil, if 293 | the build failed. 294 | 295 | Expect sources to be in BUILD-DIRECTORY. If nil, search for it 296 | using `pdf-tools-locate-build-directory'. 297 | 298 | See `pdf-tools-install' for the SKIP-DEPENDENCIES-P and 299 | FORCE-DEPENDENCIES-P arguments. 300 | 301 | Returns the buffer of the compilation process." 302 | 303 | (unless callback (setq callback #'ignore)) 304 | (unless build-directory 305 | (setq build-directory (pdf-tools-locate-build-directory))) 306 | (cl-check-type target-directory file-directory) 307 | (setq target-directory (file-name-as-directory 308 | (expand-file-name target-directory))) 309 | (cl-check-type build-directory (and (not null) file-directory)) 310 | (when (and skip-dependencies-p force-dependencies-p) 311 | (error "Can't simultaneously skip and force dependencies")) 312 | (let* ((compilation-auto-jump-to-first-error nil) 313 | (compilation-scroll-output t) 314 | (shell-file-name (pdf-tools-find-bourne-shell)) 315 | (shell-command-switch "-c") 316 | (process-environment process-environment) 317 | (default-directory build-directory) 318 | (autobuild (shell-quote-argument 319 | (expand-file-name "autobuild" build-directory))) 320 | (msys2-p (equal "bash.exe" (file-name-nondirectory shell-file-name)))) 321 | (unless shell-file-name 322 | (error "No suitable shell found")) 323 | (when msys2-p 324 | (push "BASH_ENV=/etc/profile" process-environment)) 325 | (let ((executable 326 | (expand-file-name 327 | (concat "epdfinfo" (and (eq system-type 'windows-nt) ".exe")) 328 | target-directory)) 329 | (compilation-buffer 330 | (compilation-start 331 | (format "%s -i %s%s%s" 332 | autobuild 333 | (shell-quote-argument target-directory) 334 | (cond 335 | (skip-dependencies-p " -D") 336 | (force-dependencies-p " -d") 337 | (t "")) 338 | (if pdf-tools-installer-os (concat " --os " pdf-tools-installer-os) "")) 339 | t))) 340 | ;; In most cases user-input is required, so select the window. 341 | (if (get-buffer-window compilation-buffer) 342 | (select-window (get-buffer-window compilation-buffer)) 343 | (pop-to-buffer compilation-buffer)) 344 | (with-current-buffer compilation-buffer 345 | (setq-local compilation-error-regexp-alist nil) 346 | (add-hook 'compilation-finish-functions 347 | (lambda (_buffer status) 348 | (funcall callback 349 | (and (equal status "finished\n") 350 | executable))) 351 | nil t) 352 | (current-buffer))))) 353 | 354 | 355 | ;; * ================================================================== * 356 | ;; * Initialization 357 | ;; * ================================================================== * 358 | 359 | ;;;###autoload 360 | (defun pdf-tools-install (&optional no-query-p skip-dependencies-p 361 | no-error-p force-dependencies-p) 362 | "Install PDF-Tools in all current and future PDF buffers. 363 | 364 | If the `pdf-info-epdfinfo-program' is not running or does not 365 | appear to be working, attempt to rebuild it. If this build 366 | succeeded, continue with the activation of the package. 367 | Otherwise fail silently, i.e. no error is signaled. 368 | 369 | Build the program (if necessary) without asking first, if 370 | NO-QUERY-P is non-nil. 371 | 372 | Don't attempt to install system packages, if SKIP-DEPENDENCIES-P 373 | is non-nil. 374 | 375 | Do not signal an error in case the build failed, if NO-ERROR-P is 376 | non-nil. 377 | 378 | Attempt to install system packages (even if it is deemed 379 | unnecessary), if FORCE-DEPENDENCIES-P is non-nil. 380 | 381 | Note that SKIP-DEPENDENCIES-P and FORCE-DEPENDENCIES-P are 382 | mutually exclusive. 383 | 384 | Note further, that you can influence the installation directory 385 | by setting `pdf-info-epdfinfo-program' to an appropriate 386 | value (e.g. ~/bin/epdfinfo) before calling this function. 387 | 388 | See `pdf-view-mode' and `pdf-tools-enabled-modes'." 389 | (interactive) 390 | (if (or (pdf-info-running-p) 391 | (ignore-errors (pdf-info-check-epdfinfo) t)) 392 | (pdf-tools-install-noverify) 393 | (let ((target-directory 394 | (or (and (stringp pdf-info-epdfinfo-program) 395 | (file-name-directory 396 | pdf-info-epdfinfo-program)) 397 | pdf-tools-directory))) 398 | (if (or no-query-p 399 | (y-or-n-p "Need to (re)build the epdfinfo program, do it now ?")) 400 | (pdf-tools-build-server 401 | target-directory 402 | skip-dependencies-p 403 | force-dependencies-p 404 | (lambda (executable) 405 | (let ((msg (format 406 | "Building the PDF Tools server %s" 407 | (if executable "succeeded" "failed")))) 408 | (if (not executable) 409 | (funcall (if no-error-p #'message #'error) "%s" msg) 410 | (message "%s" msg) 411 | (setq pdf-info-epdfinfo-program executable) 412 | (let ((pdf-info-restart-process-p t)) 413 | (pdf-tools-install-noverify)))))) 414 | (message "PDF Tools not activated"))))) 415 | 416 | (defun pdf-tools-install-noverify () 417 | "Like `pdf-tools-install', but skip checking `pdf-info-epdfinfo-program'." 418 | (add-to-list 'auto-mode-alist pdf-tools-auto-mode-alist-entry) 419 | (add-to-list 'magic-mode-alist pdf-tools-magic-mode-alist-entry) 420 | ;; FIXME: Generalize this sometime. 421 | (when (memq 'pdf-occur-global-minor-mode 422 | pdf-tools-enabled-modes) 423 | (pdf-occur-global-minor-mode 1)) 424 | (when (memq 'pdf-virtual-global-minor-mode 425 | pdf-tools-enabled-modes) 426 | (pdf-virtual-global-minor-mode 1)) 427 | (add-hook 'pdf-view-mode-hook 'pdf-tools-enable-minor-modes) 428 | (dolist (buf (buffer-list)) 429 | (with-current-buffer buf 430 | (when (and (not (derived-mode-p 'pdf-view-mode)) 431 | (pdf-tools-pdf-buffer-p) 432 | (buffer-file-name)) 433 | (pdf-view-mode))))) 434 | 435 | (defun pdf-tools-uninstall () 436 | "Uninstall PDF-Tools in all current and future PDF buffers." 437 | (interactive) 438 | (pdf-info-quit) 439 | (setq-default auto-mode-alist 440 | (remove pdf-tools-auto-mode-alist-entry auto-mode-alist)) 441 | (setq-default magic-mode-alist 442 | (remove pdf-tools-magic-mode-alist-entry magic-mode-alist)) 443 | (pdf-occur-global-minor-mode -1) 444 | (pdf-virtual-global-minor-mode -1) 445 | (remove-hook 'pdf-view-mode-hook 'pdf-tools-enable-minor-modes) 446 | (dolist (buf (buffer-list)) 447 | (with-current-buffer buf 448 | (when (pdf-util-pdf-buffer-p buf) 449 | (pdf-tools-disable-minor-modes pdf-tools-modes) 450 | (normal-mode))))) 451 | 452 | (defun pdf-tools-pdf-buffer-p (&optional buffer) 453 | "Return non-nil if BUFFER contains a PDF document." 454 | (save-current-buffer 455 | (when buffer (set-buffer buffer)) 456 | (save-excursion 457 | (save-restriction 458 | (widen) 459 | (goto-char 1) 460 | (looking-at "%PDF"))))) 461 | 462 | (defun pdf-tools-assert-pdf-buffer (&optional buffer) 463 | (unless (pdf-tools-pdf-buffer-p buffer) 464 | (error "Buffer does not contain a PDF document"))) 465 | 466 | (defun pdf-tools-set-modes-enabled (enable &optional modes) 467 | (dolist (m (or modes pdf-tools-enabled-modes)) 468 | (let ((enabled-p (and (boundp m) 469 | (symbol-value m)))) 470 | (unless (or (and enabled-p enable) 471 | (and (not enabled-p) (not enable))) 472 | (funcall m (if enable 1 -1)))))) 473 | 474 | ;;;###autoload 475 | (defun pdf-tools-enable-minor-modes (&optional modes) 476 | "Enable MODES in the current buffer. 477 | 478 | MODES defaults to `pdf-tools-enabled-modes'." 479 | (interactive) 480 | (pdf-util-assert-pdf-buffer) 481 | (pdf-tools-set-modes-enabled t modes) 482 | (run-hooks 'pdf-tools-enabled-hook)) 483 | 484 | (defun pdf-tools-disable-minor-modes (&optional modes) 485 | "Disable MODES in the current buffer. 486 | 487 | MODES defaults to `pdf-tools-enabled-modes'." 488 | (interactive) 489 | (pdf-tools-set-modes-enabled nil modes)) 490 | 491 | (declare-function pdf-occur-global-minor-mode "pdf-occur.el") 492 | (declare-function pdf-virtual-global-minor-mode "pdf-virtual.el") 493 | 494 | ;;;###autoload 495 | (defun pdf-tools-help () 496 | (interactive) 497 | (help-setup-xref (list #'pdf-tools-help) 498 | (called-interactively-p 'interactive)) 499 | (with-help-window (help-buffer) 500 | (princ "PDF Tools Help\n\n") 501 | (princ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n") 502 | (dolist (m (cons 'pdf-view-mode 503 | (sort (copy-sequence pdf-tools-modes) 'string<))) 504 | (princ (format "`%s' is " m)) 505 | (describe-function-1 m) 506 | (terpri) (terpri) 507 | (princ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n")))) 508 | 509 | 510 | ;; * ================================================================== * 511 | ;; * Debugging 512 | ;; * ================================================================== * 513 | 514 | (defvar pdf-tools-debug nil 515 | "Non-nil, if debugging PDF Tools.") 516 | 517 | (defun pdf-tools-toggle-debug () 518 | (interactive) 519 | (setq pdf-tools-debug (not pdf-tools-debug)) 520 | (when (called-interactively-p 'any) 521 | (message "Toggled debugging %s" (if pdf-tools-debug "on" "off")))) 522 | 523 | (provide 'pdf-tools) 524 | 525 | ;;; pdf-tools.el ends here 526 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | .deps/ 3 | Makefile 4 | Makefile.in 5 | aclocal.m4 6 | autom4te.cache/ 7 | config.h 8 | config.h.in 9 | config.log 10 | config.status 11 | configure 12 | depcomp 13 | epdfinfo 14 | install-sh 15 | libsynctex.a 16 | missing 17 | stamp-h1 18 | ar-lib 19 | compile 20 | config.h.in~ 21 | .clang_complete 22 | callgrind.out.* 23 | config.guess 24 | config.sub 25 | TAGS 26 | -------------------------------------------------------------------------------- /server/Makefile.am: -------------------------------------------------------------------------------- 1 | bin_PROGRAMS = epdfinfo 2 | epdfinfo_CFLAGS = -Wall $(glib_CFLAGS) $(poppler_glib_CFLAGS) $(poppler_CFLAGS) \ 3 | $(png_CFLAGS) 4 | epdfinfo_CXXFLAGS = -Wall $(epdfinfo_CFLAGS) 5 | epdfinfo_LDADD = $(glib_LIBS) $(poppler_glib_LIBS) $(poppler_LIBS) \ 6 | $(png_LIBS) libsynctex.a $(zlib_LIBS) 7 | epdfinfo_SOURCES = epdfinfo.c epdfinfo.h poppler-hack.cc 8 | 9 | noinst_LIBRARIES = libsynctex.a 10 | libsynctex_a_SOURCES = synctex_parser.c synctex_parser_utils.c synctex_parser.h \ 11 | synctex_parser_local.h synctex_parser_utils.h 12 | libsynctex_a_CFLAGS = -w $(zlib_CFLAGS) -DSYNCTEX_USE_LOCAL_HEADER 13 | 14 | if HAVE_W32 15 | epdfinfo_LDADD += -lshlwapi 16 | endif 17 | 18 | SYNCTEX_UPSTREAM = svn://tug.org/texlive/trunk/Build/source/texk/web2c/synctexdir 19 | SYNCTEX_FILES = synctex_parser.c \ 20 | synctex_parser.h \ 21 | synctex_parser_readme.txt \ 22 | synctex_parser_utils.c \ 23 | synctex_parser_utils.h \ 24 | synctex_parser_version.txt \ 25 | synctex_version.h \ 26 | synctex_parser_advanced.h 27 | 28 | 29 | check-local: 30 | @if $(MAKE) --version 2>&1 | grep -q GNU; then \ 31 | cd test && $(MAKE) $(AM_MAKEFLAGS); \ 32 | else \ 33 | echo "Skipping tests in server/test (requires GNU make)"; \ 34 | fi 35 | 36 | synctex-pull: 37 | @if [ -n "$$(git status --porcelain)" ]; then \ 38 | git status; \ 39 | echo "Not checking-out files into a dirty work-directory"; \ 40 | false; \ 41 | fi 42 | for file in $(SYNCTEX_FILES); do \ 43 | svn export --force $(SYNCTEX_UPSTREAM)/$$file; \ 44 | done 45 | -------------------------------------------------------------------------------- /server/autobuild: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## 4 | ## Installs package dependencies and builds the application. 5 | ## 6 | 7 | # Don't exit if some command fails. 8 | set +e 9 | # Disable file globbing. 10 | set -f 11 | 12 | # Boolean variables are true if non-empty and false otherwise. 13 | 14 | # Command to install packages. 15 | PKGCMD= 16 | # Args to pass to $PKGCMD. 17 | PKGARGS= 18 | # Required packages. 19 | PACKAGES= 20 | # Whether package installation requires root permissions. 21 | PKG_INSTALL_AS_ROOT=true 22 | # Whether to skip package installation altogether. 23 | PKG_INSTALL_SKIP= 24 | # Whether to force package installation, even if it does not seem 25 | # necessary. 26 | PKG_INSTALL_FORCE= 27 | # Only test if the OS is handled by this script. 28 | DRY_RUN= 29 | # If and where to install the program. 30 | INSTALL_DIR= 31 | # Whether we can install packages. 32 | OS_IS_HANDLED=true 33 | # Which OSs installer to use 34 | OS= 35 | 36 | ## +-----------------------------------------------------------+ 37 | ## * Utility Functions 38 | ## +-----------------------------------------------------------+ 39 | 40 | usage() 41 | { 42 | cat <()$\`"'\'' ]/\\&/g') 85 | if [ -z "$quoted" ]; then 86 | quoted=$qarg 87 | else 88 | quoted="$quoted $qarg" 89 | fi 90 | done 91 | printf "%s" "$quoted" 92 | } 93 | 94 | # Attempt to exec $@ as root. 95 | exec_privileged() { 96 | if [ -z "$1" ]; then 97 | echo "internal error: command is empty" 98 | exit 2 99 | fi 100 | if [ -w / ]; then 101 | "$@" 102 | elif which sudo >/dev/null 2>&1; then 103 | sudo -- "$@" 104 | retval=$? 105 | sudo -k 106 | return $retval 107 | elif which su >/dev/null 2>&1; then 108 | su -c "$(quote "$@")" 109 | else 110 | echo "No such program: sudo or su" 111 | exit 1 112 | fi 113 | } 114 | 115 | # Test if $1 is in PATH or exit with a failure status. 116 | assert_program() 117 | { 118 | if ! which "$1" >/dev/null 2>&1; then 119 | echo "No such program: $1" 120 | exit 1 121 | fi 122 | } 123 | 124 | # Source filename $1 and echo variable $2. 125 | source_var() 126 | { 127 | if ! [ -f "$1" ] || ! [ -r "$1" ] || [ -z "$2" ]; then 128 | return 1 129 | fi 130 | # shellcheck source=/dev/null 131 | . "$1" 132 | eval "printf '%s\n' \$$2" 133 | return 0 134 | } 135 | 136 | exit_success() 137 | { 138 | echo "===========================" 139 | echo " Build succeeded. :O) " 140 | echo "===========================" 141 | exit 0 142 | } 143 | 144 | exit_fail() 145 | { 146 | echo "===========================" 147 | echo " Build failed. ;o( " 148 | echo "===========================" 149 | if [ -z "$PKG_INSTALL_FORCE" ]; then 150 | echo "Note: maybe try the '-d' option." 151 | fi 152 | exit 1 153 | } 154 | 155 | # Return 0, if all required packages seem to be installed. 156 | have_packages_installed() 157 | { 158 | { 159 | which pkg-config || return 1 160 | if ! [ -f configure ];then 161 | which autoreconf || return 1 162 | which automake || return 1 163 | fi 164 | for lib in libpng glib-2.0 poppler poppler-glib zlib; do 165 | pkg-config --exists $lib || return 1 166 | done 167 | which make || return 1 168 | which gcc || which cc || return 1 169 | which g++ || which c++ || return 1 170 | cc $(pkg-config --cflags poppler) -o /dev/null -E - 2>/dev/null < 172 | EOF 173 | [ $? -eq 0 ] || return 1 174 | return 0 175 | } >/dev/null 2>&1 176 | } 177 | 178 | handle_options() 179 | { 180 | while [ $# -gt 0 ]; do 181 | case $1 in 182 | --help) usage 0;; 183 | -n) DRY_RUN=true;; 184 | -d) PKG_INSTALL_FORCE=true ;; 185 | -D) PKG_INSTALL_SKIP=true ;; 186 | -i) 187 | shift 188 | [ $# -gt 0 ] || usage 1 189 | if [ "${1%%/}" != "${PWD%%/}" ]; then 190 | INSTALL_DIR=$1 191 | fi ;; 192 | --os) 193 | shift 194 | [ $# -gt 0 ] || usage 1 195 | OS="$1" 196 | ;; 197 | *) usage 1 ;; 198 | esac 199 | shift 200 | done 201 | if [ -n "$PKG_INSTALL_SKIP" ] && [ -n "$PKG_INSTALL_FORCE" ]; then 202 | usage 1 203 | fi 204 | } 205 | 206 | ## +-----------------------------------------------------------+ 207 | ## * OS Functions 208 | ## +-----------------------------------------------------------+ 209 | 210 | # Archlinux 211 | os_arch() { 212 | if ! [ -e "/etc/arch-release" ]; then 213 | return 1; 214 | fi 215 | PKGCMD=pacman 216 | PKGARGS="-S --needed" 217 | PACKAGES="base-devel libpng zlib poppler-glib" 218 | return 0; 219 | } 220 | 221 | # CentOS 222 | os_centos() { 223 | if ! [ -e "/etc/centos-release" ]; then 224 | return 1 225 | fi 226 | PKGCMD=yum 227 | if yum help install-n >/dev/null 2>&1; then 228 | PKGARGS=install-n 229 | else 230 | PKGARGS=install 231 | fi 232 | PACKAGES="autoconf 233 | automake 234 | gcc 235 | gcc-c++ 236 | libpng-devel 237 | make 238 | pkgconfig 239 | poppler-devel 240 | poppler-glib-devel 241 | zlib-devel" 242 | return 0 243 | } 244 | 245 | # FreeBSD 246 | os_freebsd() { 247 | if ! which uname >/dev/null 2>&1 || [ "$(uname -s)" != "FreeBSD" ]; then 248 | return 1 249 | fi 250 | PKGCMD=pkg 251 | PKGARGS=install 252 | PACKAGES="autotools poppler-glib png pkgconf" 253 | return 0 254 | } 255 | 256 | # OpenBSD 257 | os_openbsd() { 258 | if ! which uname >/dev/null 2>&1 || [ "$(uname -s)" != "OpenBSD" ]; then 259 | return 1 260 | fi 261 | PKGCMD=pkg_add 262 | PKGARGS="-uU" 263 | PACKAGES="autoconf-2.69p2 automake-1.15.1 poppler poppler-utils png" 264 | export AUTOCONF_VERSION=2.69 265 | export AUTOMAKE_VERSION=1.15 266 | if whereis clang++ ;then 267 | export CXX=clang++ 268 | elif whereis eg++ ;then 269 | export CXX=eg++ 270 | else 271 | export CXX=eg++ 272 | PACKAGES="${PACKAGES} g++" 273 | fi 274 | export CXXFLAGS="-std=c++11 -I/usr/local/include/poppler -I/usr/local/include" 275 | return 0 276 | } 277 | 278 | # Fedora 279 | os_fedora() { 280 | if ! [ -e "/etc/fedora-release" ]; then 281 | return 1 282 | fi 283 | PKGCMD=dnf 284 | PKGARGS=install 285 | PACKAGES="autoconf 286 | automake 287 | gcc 288 | gcc-c++ 289 | libpng-devel 290 | make 291 | poppler-devel 292 | poppler-glib-devel 293 | zlib-devel" 294 | VERSION=$(source_var /etc/os-release VERSION_ID) 295 | if [ -n "$VERSION" ] && [ "$VERSION" -ge 26 ]; then 296 | PACKAGES="$PACKAGES pkgconf" 297 | else 298 | PACKAGES="$PACKAGES pkgconfig" 299 | fi 300 | return 0 301 | } 302 | 303 | # Debian/Ubuntu 304 | os_debian() { 305 | if ! [ -e "/etc/debian_version" ]; then 306 | return 1 307 | fi 308 | PACKAGES="autoconf 309 | automake 310 | g++ 311 | gcc 312 | libpng-dev 313 | libpoppler-dev 314 | libpoppler-glib-dev 315 | libpoppler-private-dev 316 | libz-dev 317 | make 318 | pkg-config" 319 | PKGCMD=apt-get 320 | PKGARGS=install 321 | return 0 322 | } 323 | 324 | # Msys2 325 | os_msys2() { 326 | if [ -z "$MSYSTEM" ] || ! [ -r "/etc/profile" ]; then 327 | return 1 328 | fi 329 | case $MSYSTEM in 330 | MINGW64) 331 | PACKAGES="base-devel 332 | mingw-w64-x86_64-libpng 333 | mingw-w64-x86_64-poppler 334 | mingw-w64-x86_64-toolchain 335 | mingw-w64-x86_64-zlib" ;; 336 | MINGW32) 337 | PACKAGES="base-devel 338 | mingw-w64-i686-libpng 339 | mingw-w64-i686-poppler 340 | mingw-w64-i686-toolchain 341 | mingw-w64-i686-zlib" ;; 342 | MSYS) 343 | case $(uname -m) in 344 | x86_64) 345 | MSYSTEM=MINGW64 ;; 346 | *) 347 | MSYSTEM=MINGW32 ;; 348 | esac 349 | export MSYSTEM 350 | # shellcheck source=/dev/null 351 | . /etc/profile 352 | eval "exec $(quote "$0" "$@")" ;; 353 | *) 354 | echo "Unrecognized MSYSTEM value: $MSYSTEM" 355 | exit 1 ;; 356 | esac 357 | PKGCMD=pacman 358 | PKGARGS="-S --needed" 359 | PKG_INSTALL_AS_ROOT= 360 | return 0 361 | } 362 | 363 | # MacOS 364 | os_macos() { 365 | if ! which uname >/dev/null 2>&1 || [ "$(uname -s)" != "Darwin" ]; then 366 | return 1 367 | elif which brew >/dev/null 2>&1; then 368 | PKGCMD=brew 369 | PKGARGS=install 370 | PACKAGES="pkg-config poppler automake" 371 | PKG_INSTALL_AS_ROOT= 372 | # homebrew install libffi as keg-only, meaning we need to set 373 | # PKG_CONFIG_PATH manually so configure can find it 374 | export PKG_CONFIG_PATH="$(brew --prefix libffi)/lib/pkgconfig/" 375 | elif which port >/dev/null 2>&1; then 376 | PKGCMD=port 377 | PKGARGS=install 378 | PACKAGES="pkgconfig poppler automake libpng" 379 | else 380 | return 1 381 | fi 382 | return 0 383 | } 384 | 385 | # NixOS 386 | os_nixos() { 387 | # Already in the nix-shell. 388 | if [ -n "$AUTOBUILD_NIX_SHELL" ]; then 389 | return 0 390 | fi 391 | if ! which nix-shell >/dev/null 2>&1; then 392 | return 1 393 | fi 394 | if [ -n "$DRY_RUN" ]; then 395 | return 0 396 | fi 397 | command="AUTOBUILD_NIX_SHELL=true" 398 | command="$command;export AUTOBUILD_NIX_SHELL" 399 | command="$command;$(quote "$0" "$@")" 400 | exec nix-shell --pure --command "$command" \ 401 | -p gcc gnumake automake autoconf pkgconfig libpng zlib poppler 402 | } 403 | 404 | # Gentoo 405 | os_gentoo() { 406 | if ! [ -e "/etc/gentoo-release" ]; then 407 | return 1 408 | fi 409 | PKGCMD=emerge 410 | PKGARGS=--noreplace 411 | PACKAGES="app-text/poppler 412 | dev-util/pkgconfig 413 | media-libs/libpng 414 | sys-devel/autoconf 415 | sys-devel/automake 416 | sys-devel/gcc 417 | sys-devel/make 418 | sys-libs/zlib" 419 | return 0 420 | } 421 | 422 | # By Parameter --os 423 | os_argument() { 424 | [ -z "$OS" ] && return 1 425 | case $OS in 426 | macos) os_macos "$@";; 427 | freebsd) os_freebsd "$@";; 428 | arch) os_arch "$@";; 429 | centos) os_centos "$@";; 430 | openbsd) os_openbsd "$@";; 431 | fedora) os_fedora "$@";; 432 | debian) os_debian "$@";; 433 | gentoo) os_gentoo "$@";; 434 | msys2) os_msys2 "$@";; 435 | nixos) os_nixos "$@";; 436 | *) echo "Invalid --os argument: $OS" 437 | exit 1 438 | esac || { 439 | echo "Unable to install on this system as $OS" 440 | exit 1 441 | } 442 | } 443 | 444 | ## +-----------------------------------------------------------+ 445 | ## * Figure out were we are, install deps and build the program 446 | ## +-----------------------------------------------------------+ 447 | 448 | handle_options "$@" 449 | 450 | os_argument "$@" || \ 451 | os_macos "$@" || \ 452 | os_freebsd "$@" || \ 453 | os_arch "$@" || \ 454 | os_centos "$@" || \ 455 | os_openbsd "$@" || \ 456 | os_fedora "$@" || \ 457 | os_debian "$@" || \ 458 | os_gentoo "$@" || \ 459 | os_msys2 "$@" || \ 460 | os_nixos "$@" || \ 461 | { 462 | OS_IS_HANDLED= 463 | if [ -z "$DRY_RUN" ]; then 464 | echo "Failed to recognize this system, trying to continue." 465 | fi 466 | } 467 | 468 | if [ -n "$DRY_RUN" ]; then 469 | [ -n "$OS_IS_HANDLED" ] 470 | exit $? 471 | fi 472 | 473 | if [ -n "$PKGCMD" ];then 474 | echo "---------------------------" 475 | echo " Installing packages " 476 | echo "---------------------------" 477 | if [ -n "$PKG_INSTALL_SKIP" ]; then 478 | echo "Skipping package installation (as requested)" 479 | elif [ -z "$PKG_INSTALL_FORCE" ] && have_packages_installed; then 480 | echo "Skipping package installation (already installed)" 481 | else 482 | assert_program "$PKGCMD" 483 | echo "$PKGCMD $PKGARGS $PACKAGES" 484 | if [ -n "$PKG_INSTALL_AS_ROOT" ]; then 485 | exec_privileged $PKGCMD $PKGARGS $PACKAGES 486 | else 487 | $PKGCMD $PKGARGS $PACKAGES 488 | fi 489 | fi 490 | echo 491 | fi 492 | 493 | echo "---------------------------" 494 | echo " Configuring and compiling " 495 | echo "---------------------------" 496 | 497 | # Try to be in the correct directory. 498 | if which dirname >/dev/null 2>&1; then 499 | cd "$(dirname "$0")" || { 500 | echo "Failed to change into the source directory" 501 | exit 1 502 | } 503 | fi 504 | 505 | # Create the configure script. 506 | if ! [ -f ./configure ]; then 507 | assert_program autoreconf 508 | echo "autoreconf -i" 509 | autoreconf -i 510 | [ -f ./configure ] || exit_fail 511 | fi 512 | 513 | # Build the program. 514 | if [ -n "$INSTALL_DIR" ]; then 515 | prefix=--bindir=$INSTALL_DIR 516 | fi 517 | 518 | echo "./configure -q $prefix && make -s" 519 | eval "./configure -q $(quote "$prefix") && make -s || exit_fail" 520 | echo 521 | if [ -n "$INSTALL_DIR" ]; then 522 | echo "---------------------------" 523 | echo " Installing " 524 | echo "---------------------------" 525 | echo make -s install 526 | if mkdir -p -- "$INSTALL_DIR" && [ -w "$INSTALL_DIR" ]; then 527 | make install || exit_fail 528 | else 529 | exec_privileged make install || exit_fail 530 | fi 531 | # Copy dynamic libraries on windows. 532 | if [ -f epdfinfo.exe ]; then 533 | assert_program awk 534 | assert_program ldd 535 | for dll in $(ldd epdfinfo.exe | awk '/\/mingw/ {print $3}'); do 536 | cp -u "$dll" "$INSTALL_DIR" 537 | done 538 | fi 539 | echo 540 | fi 541 | exit_success 542 | 543 | # Local Variables: 544 | # compile-command: "shellcheck autobuild" 545 | # End: 546 | -------------------------------------------------------------------------------- /server/autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Running autoreconf..." 4 | 5 | autoreconf -i 6 | -------------------------------------------------------------------------------- /server/configure.ac: -------------------------------------------------------------------------------- 1 | # -*- Autoconf -*- 2 | # Process this file with autoconf to produce a configure script. 3 | 4 | AC_PREREQ([2.67]) 5 | AC_INIT([epdfinfo], 1.0, [politza@fh-trier.de]) 6 | AM_INIT_AUTOMAKE([-Wall -Wno-override foreign silent-rules]) 7 | AC_CONFIG_SRCDIR([epdfinfo.h]) 8 | AC_CONFIG_HEADERS([config.h]) 9 | 10 | # Checks for programs. 11 | AC_PROG_CC 12 | AM_PROG_CC_C_O 13 | AC_PROG_CXX 14 | AC_PROG_RANLIB 15 | AM_PROG_AR 16 | 17 | # Checks for libraries. 18 | HAVE_POPPLER_FIND_OPTS="no (requires poppler-glib >= 0.22)" 19 | HAVE_POPPLER_ANNOT_WRITE="no (requires poppler-glib >= 0.19.4)" 20 | HAVE_POPPLER_ANNOT_MARKUP="no (requires poppler-glib >= 0.26)" 21 | 22 | PKG_CHECK_MODULES([png], [libpng]) 23 | PKG_CHECK_MODULES([glib], [glib-2.0]) 24 | PKG_CHECK_MODULES([poppler], [poppler]) 25 | PKG_CHECK_MODULES([poppler_glib], [poppler-glib >= 0.16.0]) 26 | PKG_CHECK_EXISTS([poppler-glib >= 0.19.4], [HAVE_POPPLER_ANNOT_WRITE=yes]) 27 | PKG_CHECK_EXISTS([poppler-glib >= 0.22], [HAVE_POPPLER_FIND_OPTS=yes]) 28 | PKG_CHECK_EXISTS([poppler-glib >= 0.26], [HAVE_POPPLER_ANNOT_MARKUP=yes]) 29 | PKG_CHECK_MODULES([zlib], [zlib]) 30 | 31 | AC_COMPILE_IFELSE( 32 | [AC_LANG_PROGRAM([[ 33 | #ifndef _WIN32 34 | error 35 | #endif 36 | ]])], [have_w32=true], [have_w32=false]) 37 | AM_CONDITIONAL(HAVE_W32, [test "$have_w32" = true]) 38 | 39 | if test "$have_w32" = true; then 40 | if test "$MSYSTEM" = MINGW32 -o "$MSYSTEM" = MINGW64; then 41 | # glib won't work properly on msys2 without it. 42 | CFLAGS="-D__USE_MINGW_ANSI_STDIO=1 $CFLAGS" 43 | fi 44 | fi 45 | 46 | SAVED_CPPFLAGS=$CPPFLAGS 47 | CPPFLAGS=$poppler_CFLAGS 48 | AC_LANG_PUSH([C++]) 49 | # Check if we can use the -std=c++11 option. 50 | m4_include([m4/ax_check_compile_flag.m4]) 51 | AX_CHECK_COMPILE_FLAG([-std=c++11], [HAVE_STD_CXX11=yes]) 52 | 53 | if test "$HAVE_STD_CXX11" = yes; then 54 | CXXFLAGS="-std=c++11 $CXXFLAGS" 55 | fi 56 | # Check for private poppler header. 57 | AC_CHECK_HEADERS([Annot.h PDFDocEncoding.h], [], 58 | AC_MSG_ERROR([cannot find necessary poppler-private header (see README.org)])) 59 | AC_LANG_POP([C++]) 60 | CPPFLAGS=$SAVED_CPPFLAGS 61 | 62 | # Setup compile time features. 63 | if test "$HAVE_POPPLER_FIND_OPTS" = yes; then 64 | AC_DEFINE([HAVE_POPPLER_FIND_OPTS],1, 65 | [Define to 1 to enable case sensitive searching (requires poppler-glib >= 0.22).]) 66 | fi 67 | 68 | if test "$HAVE_POPPLER_ANNOT_WRITE" = yes; then 69 | AC_DEFINE([HAVE_POPPLER_ANNOT_WRITE],1, 70 | [Define to 1 to enable writing of annotations (requires poppler-glib >= 0.19.4).]) 71 | fi 72 | 73 | if test "$HAVE_POPPLER_ANNOT_MARKUP" = yes; then 74 | AC_DEFINE([HAVE_POPPLER_ANNOT_MARKUP],1, 75 | [Define to 1 to enable adding of markup annotations (requires poppler-glib >= 0.26).]) 76 | fi 77 | 78 | AC_CANONICAL_HOST 79 | # Checks for header files. 80 | AC_CHECK_HEADERS([stdlib.h string.h strings.h err.h]) 81 | 82 | AC_MSG_CHECKING([for error.h]) 83 | SAVED_CFLAGS=$CFLAGS 84 | CFLAGS="$poppler_CFLAGS $poppler_glib_CFLAGS" 85 | AC_LANG_PUSH([C]) 86 | AC_COMPILE_IFELSE([AC_LANG_PROGRAM([ 87 | #include 88 | ],[error (0, 0, "");])], 89 | [AC_DEFINE([HAVE_ERROR_H],1, [Define to 1 if error.h is usable.]) 90 | AC_MSG_RESULT([yes])], 91 | AC_MSG_RESULT([no])) 92 | AC_LANG_POP([C]) 93 | CFLAGS=$SAVED_CFLAGS 94 | 95 | # Checks for typedefs, structures, and compiler characteristics. 96 | AC_TYPE_SIZE_T 97 | AC_TYPE_SSIZE_T 98 | AC_CHECK_TYPES([ptrdiff_t]) 99 | AC_C_BIGENDIAN 100 | 101 | # Checks for library functions. 102 | AC_FUNC_ERROR_AT_LINE 103 | AC_FUNC_STRTOD 104 | AC_CHECK_FUNCS([strcspn strtol getline]) 105 | 106 | AC_CONFIG_FILES([Makefile]) 107 | AC_OUTPUT 108 | 109 | echo 110 | echo "Is case-sensitive searching enabled ? ${HAVE_POPPLER_FIND_OPTS}" 111 | echo "Is modifying text annotations enabled ? ${HAVE_POPPLER_ANNOT_WRITE}" 112 | echo "Is modifying markup annotations enabled ? ${HAVE_POPPLER_ANNOT_MARKUP}" 113 | echo 114 | -------------------------------------------------------------------------------- /server/epdfinfo.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013, 2014 Andreas Politz 2 | 3 | // Author: Andreas Politz 4 | 5 | // This program is free software; you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | 18 | #ifndef _EPDF_H_ 19 | #define _EPDF_H_ _EPDF_H_ 20 | #include "config.h" 21 | #include 22 | #include 23 | #include 24 | 25 | /* Some library functions print warnings to stdout, inhibit it. */ 26 | #define DISCARD_STDOUT(saved_fd) \ 27 | do { \ 28 | int __fd; \ 29 | fflush(stdout); \ 30 | saved_fd = dup(1); \ 31 | __fd = open("/dev/null", O_WRONLY); \ 32 | dup2(__fd, 1); \ 33 | close(__fd); \ 34 | } while (0) 35 | 36 | #define UNDISCARD_STDOUT(saved_fd) \ 37 | do { \ 38 | fflush(stdout); \ 39 | dup2(saved_fd, 1); \ 40 | close(saved_fd); \ 41 | } while (0) 42 | 43 | /* Writing responses */ 44 | #define OK_BEGIN() \ 45 | do { \ 46 | puts("OK"); \ 47 | } while (0) 48 | 49 | #define OK_END() \ 50 | do { \ 51 | puts("."); \ 52 | fflush (stdout); \ 53 | } while (0) 54 | 55 | #define OK() \ 56 | do { \ 57 | puts ("OK\n."); \ 58 | fflush (stdout); \ 59 | } while (0) 60 | 61 | /* Dealing with image data. */ 62 | #ifdef WORDS_BIGENDIAN 63 | #define ARGB_TO_RGB(rgb, argb) \ 64 | do { \ 65 | rgb[0] = argb[1]; \ 66 | rgb[1] = argb[2]; \ 67 | rgb[2] = argb[3]; \ 68 | } while (0) 69 | 70 | #define ARGB_EQUAL(argb1, argb2) \ 71 | (argb1[1] == argb2[1] \ 72 | && argb1[2] == argb2[2] \ 73 | && argb1[3] == argb2[3]) 74 | 75 | #else 76 | #define ARGB_TO_RGB(rgb, argb) \ 77 | do { \ 78 | rgb[0] = argb[2]; \ 79 | rgb[1] = argb[1]; \ 80 | rgb[2] = argb[0]; \ 81 | } while (0) 82 | 83 | #define ARGB_EQUAL(argb1, argb2) \ 84 | (argb1[0] == argb2[0] \ 85 | && argb1[1] == argb2[1] \ 86 | && argb1[2] == argb2[2]) 87 | #endif 88 | 89 | #define NORMALIZE_PAGE_ARG(doc, first, last) \ 90 | *first = MAX(1, *first); \ 91 | if (*last <= 0) \ 92 | *last = poppler_document_get_n_pages (doc); \ 93 | else \ 94 | *last = MIN(*last, poppler_document_get_n_pages (doc)); 95 | 96 | /* png_jmpbuf is supposed to be not available in older versions of 97 | libpng. */ 98 | #ifndef png_jmpbuf 99 | # define png_jmpbuf(png_ptr) ((png_ptr)->jmpbuf) 100 | #endif 101 | 102 | #ifndef HAVE_ERROR_H 103 | # define error(status, errno, fmt, args...) \ 104 | do { \ 105 | int error = (errno); \ 106 | fflush (stdout); \ 107 | fprintf (stderr, "%s: " fmt, PACKAGE_NAME, ## args); \ 108 | if (error) \ 109 | fprintf (stderr, ": %s", strerror (error)); \ 110 | fprintf (stderr, "\n"); \ 111 | exit (status); \ 112 | } while (0) 113 | #endif 114 | 115 | #define internal_error(fmt, args...) \ 116 | error (2, 0, "internal error in %s: " fmt, __func__, ## args) 117 | 118 | #define error_if_not(expr) \ 119 | if (! (expr)) goto error; 120 | 121 | #define perror_if_not(expr, fmt, args...) \ 122 | do { \ 123 | if (! (expr)) \ 124 | { \ 125 | printf_error_response ((fmt), ## args); \ 126 | goto error; \ 127 | } \ 128 | } while (0) 129 | 130 | #define cerror_if_not(expr, error_msg, fmt, args...) \ 131 | do { \ 132 | if (! (expr)) \ 133 | { \ 134 | if (error_msg) \ 135 | *(error_msg) = g_strdup_printf((fmt), ## args); \ 136 | goto error; \ 137 | } \ 138 | } while (0) 139 | 140 | /* Declare commands */ 141 | #define DEC_CMD(name) \ 142 | {#name, cmd_ ## name, cmd_ ## name ## _spec, \ 143 | G_N_ELEMENTS (cmd_ ## name ## _spec)} 144 | 145 | #define DEC_CMD2(command, name) \ 146 | {name, cmd_ ## command, cmd_ ## command ## _spec, \ 147 | G_N_ELEMENTS (cmd_ ## command ## _spec)} 148 | 149 | /* Declare option */ 150 | #define DEC_DOPT(name, type, sname) \ 151 | {name, type, offsetof (document_options_t, sname)} 152 | 153 | enum suffix_char { NONE, COLON, NEWLINE}; 154 | 155 | enum image_type { PPM, PNG }; 156 | 157 | typedef struct 158 | { 159 | PopplerAnnotMapping *amap; 160 | gchar *key; 161 | } annotation_t; 162 | 163 | typedef enum 164 | { 165 | ARG_INVALID = 0, 166 | ARG_DOC, 167 | ARG_BOOL, 168 | ARG_STRING, 169 | ARG_NONEMPTY_STRING, 170 | ARG_NATNUM, 171 | ARG_EDGE, 172 | ARG_EDGE_OR_NEGATIVE, 173 | ARG_EDGES, 174 | ARG_EDGES_OR_POSITION, 175 | ARG_COLOR, 176 | ARG_REST 177 | } command_arg_type_t; 178 | 179 | typedef struct 180 | { 181 | const char *name; 182 | command_arg_type_t type; 183 | size_t offset; 184 | } document_option_t; 185 | 186 | typedef struct 187 | { 188 | PopplerColor bg, fg; 189 | gboolean usecolors; 190 | gboolean printed; 191 | } render_options_t; 192 | 193 | typedef struct 194 | { 195 | render_options_t render; 196 | } document_options_t; 197 | 198 | typedef struct 199 | { 200 | PopplerDocument *pdf; 201 | char *filename; 202 | char *passwd; 203 | struct 204 | { 205 | GHashTable *keys; /* key => page */ 206 | GList **pages; /* page array */ 207 | } annotations; 208 | document_options_t options; 209 | } document_t; 210 | 211 | typedef struct 212 | { 213 | command_arg_type_t type; 214 | union 215 | { 216 | gboolean flag; 217 | const char *string; 218 | long natnum; 219 | document_t *doc; 220 | gdouble edge; 221 | PopplerColor color; 222 | PopplerRectangle rectangle; 223 | #ifdef HAVE_POPPLER_ANNOT_MARKUP 224 | PopplerQuadrilateral quadrilateral; 225 | #endif 226 | struct 227 | { 228 | char * const *args; 229 | int nargs; 230 | } rest; 231 | } value; 232 | } command_arg_t; 233 | 234 | typedef struct 235 | { 236 | GHashTable *documents; 237 | } epdfinfo_t; 238 | 239 | typedef struct 240 | { 241 | const char *name; 242 | void (* execute) (const epdfinfo_t *ctxt, const command_arg_t *args); 243 | const command_arg_type_t *args_spec; 244 | int nargs; 245 | } command_t; 246 | 247 | /* Defined in poppler-hack.cc */ 248 | #ifdef HAVE_POPPLER_ANNOT_WRITE 249 | extern void xpoppler_annot_set_rectangle (PopplerAnnot*, PopplerRectangle*); 250 | #endif 251 | extern gchar *xpoppler_annot_markup_get_created (PopplerAnnotMarkup*); 252 | #endif /* _EPDF_H_ */ 253 | -------------------------------------------------------------------------------- /server/m4/ax_check_compile_flag.m4: -------------------------------------------------------------------------------- 1 | # =========================================================================== 2 | # https://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html 3 | # =========================================================================== 4 | # 5 | # SYNOPSIS 6 | # 7 | # AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT]) 8 | # 9 | # DESCRIPTION 10 | # 11 | # Check whether the given FLAG works with the current language's compiler 12 | # or gives an error. (Warnings, however, are ignored) 13 | # 14 | # ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on 15 | # success/failure. 16 | # 17 | # If EXTRA-FLAGS is defined, it is added to the current language's default 18 | # flags (e.g. CFLAGS) when the check is done. The check is thus made with 19 | # the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to 20 | # force the compiler to issue an error when a bad flag is given. 21 | # 22 | # INPUT gives an alternative input source to AC_COMPILE_IFELSE. 23 | # 24 | # NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this 25 | # macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG. 26 | # 27 | # LICENSE 28 | # 29 | # Copyright (c) 2008 Guido U. Draheim 30 | # Copyright (c) 2011 Maarten Bosmans 31 | # 32 | # This program is free software: you can redistribute it and/or modify it 33 | # under the terms of the GNU General Public License as published by the 34 | # Free Software Foundation, either version 3 of the License, or (at your 35 | # option) any later version. 36 | # 37 | # This program is distributed in the hope that it will be useful, but 38 | # WITHOUT ANY WARRANTY; without even the implied warranty of 39 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 40 | # Public License for more details. 41 | # 42 | # You should have received a copy of the GNU General Public License along 43 | # with this program. If not, see . 44 | # 45 | # As a special exception, the respective Autoconf Macro's copyright owner 46 | # gives unlimited permission to copy, distribute and modify the configure 47 | # scripts that are the output of Autoconf when processing the Macro. You 48 | # need not follow the terms of the GNU General Public License when using 49 | # or distributing such scripts, even though portions of the text of the 50 | # Macro appear in them. The GNU General Public License (GPL) does govern 51 | # all other use of the material that constitutes the Autoconf Macro. 52 | # 53 | # This special exception to the GPL applies to versions of the Autoconf 54 | # Macro released by the Autoconf Archive. When you make and distribute a 55 | # modified version of the Autoconf Macro, you may extend this special 56 | # exception to the GPL to apply to your modified version as well. 57 | 58 | #serial 5 59 | 60 | AC_DEFUN([AX_CHECK_COMPILE_FLAG], 61 | [AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF 62 | AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl 63 | AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [ 64 | ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS 65 | _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1" 66 | AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])], 67 | [AS_VAR_SET(CACHEVAR,[yes])], 68 | [AS_VAR_SET(CACHEVAR,[no])]) 69 | _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags]) 70 | AS_VAR_IF(CACHEVAR,yes, 71 | [m4_default([$2], :)], 72 | [m4_default([$3], :)]) 73 | AS_VAR_POPDEF([CACHEVAR])dnl 74 | ])dnl AX_CHECK_COMPILE_FLAGS 75 | -------------------------------------------------------------------------------- /server/poppler-hack.cc: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013, 2014 Andreas Politz 2 | 3 | // This program is free software; you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | extern "C" 24 | { 25 | 26 | GType poppler_annot_get_type (void) G_GNUC_CONST; 27 | GType poppler_annot_markup_get_type (void) G_GNUC_CONST; 28 | 29 | #define POPPLER_TYPE_ANNOT (poppler_annot_get_type ()) 30 | #define POPPLER_ANNOT(obj) \ 31 | (G_TYPE_CHECK_INSTANCE_CAST ((obj), POPPLER_TYPE_ANNOT, PopplerAnnot)) 32 | #define POPPLER_IS_ANNOT_MARKUP(obj) \ 33 | (G_TYPE_CHECK_INSTANCE_TYPE ((obj), POPPLER_TYPE_ANNOT_MARKUP)) 34 | #define POPPLER_TYPE_ANNOT_MARKUP (poppler_annot_markup_get_type ()) 35 | 36 | #if POPPLER_CHECK_VERSION(0,72,0) 37 | #define GET_CSTR c_str 38 | #else 39 | #define GET_CSTR getCString 40 | #endif 41 | 42 | struct PopplerAnnot 43 | { 44 | GObject parent_instance; 45 | Annot *annot; 46 | }; 47 | 48 | struct PopplerAnnotMarkup 49 | { 50 | GObject parent_instance; 51 | }; 52 | 53 | struct PopplerRectangle 54 | { 55 | double x1; 56 | double y1; 57 | double x2; 58 | double y2; 59 | }; 60 | 61 | // This function does not modify its argument s, but for 62 | // compatibility reasons (e.g. getLength in GooString.h before 2015) 63 | // with older poppler code, it can't be declared as such. 64 | char *_xpoppler_goo_string_to_utf8(/* const */ GooString *s) 65 | { 66 | char *result; 67 | 68 | if (! s) 69 | return NULL; 70 | 71 | if (s->hasUnicodeMarker()) { 72 | result = g_convert (s->GET_CSTR () + 2, 73 | s->getLength () - 2, 74 | "UTF-8", "UTF-16BE", NULL, NULL, NULL); 75 | } else { 76 | int len; 77 | gunichar *ucs4_temp; 78 | int i; 79 | 80 | len = s->getLength (); 81 | ucs4_temp = g_new (gunichar, len + 1); 82 | for (i = 0; i < len; ++i) { 83 | ucs4_temp[i] = pdfDocEncoding[(unsigned char)s->getChar(i)]; 84 | } 85 | ucs4_temp[i] = 0; 86 | 87 | result = g_ucs4_to_utf8 (ucs4_temp, -1, NULL, NULL, NULL); 88 | 89 | g_free (ucs4_temp); 90 | } 91 | 92 | return result; 93 | } 94 | #ifdef HAVE_POPPLER_ANNOT_WRITE 95 | // Set the rectangle of an annotation. It was first added in v0.26. 96 | void xpoppler_annot_set_rectangle (PopplerAnnot *a, PopplerRectangle *rectangle) 97 | { 98 | GooString *state = (GooString*) a->annot->getAppearState (); 99 | char *ustate = _xpoppler_goo_string_to_utf8 (state); 100 | 101 | a->annot->setRect (rectangle->x1, rectangle->y1, 102 | rectangle->x2, rectangle->y2); 103 | a->annot->setAppearanceState (ustate); 104 | g_free (ustate); 105 | } 106 | #endif 107 | // This function is in the library, but the enforced date parsing is 108 | // incomplete (at least in some versions), because it ignores the 109 | // timezone. 110 | gchar *xpoppler_annot_markup_get_created (PopplerAnnotMarkup *poppler_annot) 111 | { 112 | AnnotMarkup *annot; 113 | GooString *text; 114 | 115 | g_return_val_if_fail (POPPLER_IS_ANNOT_MARKUP (poppler_annot), NULL); 116 | 117 | annot = static_cast(POPPLER_ANNOT (poppler_annot)->annot); 118 | text = (GooString*) annot->getDate (); 119 | 120 | return text ? _xpoppler_goo_string_to_utf8 (text) : NULL; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /server/poppler-versions: -------------------------------------------------------------------------------- 1 | HAVE_POPPLER_ANNOT_WRITE 2 | 0.19.4 solves bug 49080, which potentially corrupts PDF files. 3 | 4 | HAVE_POPPLER_FIND_OPTS 5 | 0.22 PopplerFindFlags 6 | 0.22 poppler_page_find_text_with_options 7 | 8 | HAVE_POPPLER_ANNOT_SET_RECT 9 | 0.26 Adds function poppler_annot_set_rectangle 10 | 11 | HAVE_POPPLER_ANNOT_MARKUP 12 | 0.26 poppler_annot_text_markup_new_{highlight,squiggly,strikeout,underline} 13 | -------------------------------------------------------------------------------- /server/synctex_parser.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008-2017 jerome DOT laurens AT u-bourgogne DOT fr 3 | 4 | This file is part of the __SyncTeX__ package. 5 | 6 | [//]: # (Latest Revision: Fri Jul 14 16:20:41 UTC 2017) 7 | [//]: # (Version: 1.21) 8 | 9 | See `synctex_parser_readme.md` for more details 10 | 11 | ## License 12 | 13 | Permission is hereby granted, free of charge, to any person 14 | obtaining a copy of this software and associated documentation 15 | files (the "Software"), to deal in the Software without 16 | restriction, including without limitation the rights to use, 17 | copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | copies of the Software, and to permit persons to whom the 19 | Software is furnished to do so, subject to the following 20 | conditions: 21 | 22 | The above copyright notice and this permission notice shall be 23 | included in all copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 27 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 29 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 30 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 31 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 32 | OTHER DEALINGS IN THE SOFTWARE 33 | 34 | Except as contained in this notice, the name of the copyright holder 35 | shall not be used in advertising or otherwise to promote the sale, 36 | use or other dealings in this Software without prior written 37 | authorization from the copyright holder. 38 | 39 | ## Acknowledgments: 40 | 41 | The author received useful remarks from the __pdfTeX__ developers, especially Hahn The Thanh, 42 | and significant help from __XeTeX__ developer Jonathan Kew. 43 | 44 | ## Nota Bene: 45 | 46 | If you include or use a significant part of the __SyncTeX__ package into a software, 47 | I would appreciate to be listed as contributor and see "__SyncTeX__" highlighted. 48 | */ 49 | 50 | #ifndef __SYNCTEX_PARSER__ 51 | # define __SYNCTEX_PARSER__ 52 | 53 | #include "synctex_version.h" 54 | 55 | #ifdef __cplusplus 56 | extern "C" { 57 | #endif 58 | 59 | /* The main synctex object is a scanner. 60 | * Its implementation is considered private. 61 | * The basic workflow is 62 | * - create a "synctex scanner" with the contents of a file 63 | * - perform actions on that scanner like 64 | synctex_display_query or synctex_edit_query below. 65 | * - perform actions on nodes returned by the scanner 66 | * - free the scanner when the work is done 67 | */ 68 | typedef struct synctex_scanner_t synctex_scanner_s; 69 | typedef synctex_scanner_s * synctex_scanner_p; 70 | 71 | /** 72 | * This is the designated method to create 73 | * a new synctex scanner object. 74 | * - argument output: the pdf/dvi/xdv file associated 75 | * to the synctex file. 76 | * If necessary, it can be the tex file that 77 | * originated the synctex file but this might cause 78 | * problems if the \jobname has a custom value. 79 | * Despite this method can accept a relative path 80 | * in practice, you should only pass full paths. 81 | * The path should be encoded by the underlying 82 | * file system, assuming that it is based on 83 | * 8 bits characters, including UTF8, 84 | * not 16 bits nor 32 bits. 85 | * The last file extension is removed and 86 | * replaced by the proper extension, 87 | * either synctex or synctex.gz. 88 | * - argument build_directory: It is the directory where 89 | * all the auxiliary stuff is created. 90 | * If no synctex file is found in the same directory 91 | * as the output file, then we try to find one in 92 | * this build directory. 93 | * It is the directory where all the auxiliary 94 | * stuff is created. Sometimes, the synctex output 95 | * file and the pdf, dvi or xdv files are not 96 | * created in the same location. See MikTeX. 97 | * This directory path can be NULL, 98 | * it will be ignored then. 99 | * It can be either absolute or relative to the 100 | * directory of the output pdf (dvi or xdv) file. 101 | * Please note that this new argument is provided 102 | * as a convenience but should not be used. 103 | * Available since version 1.5. 104 | * - argument parse: In general, use 1. 105 | * Use 0 only if you do not want to parse the 106 | * content but just check for existence. 107 | * Available since version 1.5 108 | * - return: a scanner. NULL is returned in case 109 | * of an error or non existent file. 110 | */ 111 | synctex_scanner_p synctex_scanner_new_with_output_file(const char * output, const char * build_directory, int parse); 112 | 113 | /** 114 | * Designated method to delete a synctex scanner object, 115 | * including all its internal resources. 116 | * Frees all the memory, you must call it when you are finished with the scanner. 117 | * - argument scanner: a scanner. 118 | * - returns: an integer used for testing purposes. 119 | */ 120 | int synctex_scanner_free(synctex_scanner_p scanner); 121 | 122 | /** 123 | * Send this message to force the scanner to 124 | * parse the contents of the synctex output file. 125 | * Nothing is performed if the file was already parsed. 126 | * In each query below, this message is sent, 127 | * but if you need to access information more directly, 128 | * you must ensure that the parsing did occur. 129 | * Usage: 130 | * if((my_scanner = synctex_scanner_parse(my_scanner))) { 131 | * continue with my_scanner... 132 | * } else { 133 | * there was a problem 134 | * } 135 | * - returns: the argument on success. 136 | * On failure, frees scanner and returns NULL. 137 | */ 138 | synctex_scanner_p synctex_scanner_parse(synctex_scanner_p scanner); 139 | 140 | /* synctex_node_p is the type for all synctex nodes. 141 | * Its implementation is considered private. 142 | * The synctex file is parsed into a tree of nodes, either sheet, form, boxes, math nodes... */ 143 | 144 | typedef struct synctex_node_t synctex_node_s; 145 | typedef synctex_node_s * synctex_node_p; 146 | 147 | /* The main entry points. 148 | * Given the file name, a line and a column number, synctex_display_query returns the number of nodes 149 | * satisfying the contrain. Use code like 150 | * 151 | * if(synctex_display_query(scanner,name,line,column,page_hint)>0) { 152 | * synctex_node_p node; 153 | * while((node = synctex_scanner_next_result(scanner))) { 154 | * // do something with node 155 | * ... 156 | * } 157 | * } 158 | * 159 | * Please notice that since version 1.19, 160 | * there is a new argument page_hint. 161 | * The results in pages closer to page_hint are given first. 162 | * For example, one can 163 | * - highlight each resulting node in the output, using synctex_node_visible_h and synctex_node_visible_v 164 | * - highlight all the rectangles enclosing those nodes, using synctex_node_box_visible_... functions 165 | * - highlight just the character using that information 166 | * 167 | * Given the page and the position in the page, synctex_edit_query returns the number of nodes 168 | * satisfying the contrain. Use code like 169 | * 170 | * if(synctex_edit_query(scanner,page,h,v)>0) { 171 | * synctex_node_p node; 172 | * while(node = synctex_scanner_next_result(scanner)) { 173 | * // do something with node 174 | * ... 175 | * } 176 | * } 177 | * 178 | * For example, one can 179 | * - highlight each resulting line in the input, 180 | * - highlight just the character using that information 181 | * 182 | * page is 1 based 183 | * h and v are coordinates in 72 dpi unit, relative to the top left corner of the page. 184 | * If you make a new query, the result of the previous one is discarded. If you need to make more than one query 185 | * in parallel, use the iterator API exposed in 186 | * the synctex_parser_private.h header. 187 | * If one of this function returns a negative integer, 188 | * it means that an error occurred. 189 | * 190 | * Both methods are conservative, in the sense that matching is weak. 191 | * If the exact column number is not found, there will be an answer with the whole line. 192 | * 193 | * Sumatra-PDF, Skim, iTeXMac2, TeXShop and Texworks are examples of open source software that use this library. 194 | * You can browse their code for a concrete implementation. 195 | */ 196 | typedef long synctex_status_t; 197 | /* The page_hint argument is used to resolve ambiguities. 198 | * Whenever, different matches occur, the ones closest 199 | * to the page will be given first. Pass a negative number 200 | * when in doubt. Using pdf forms may lead to ambiguities. 201 | */ 202 | synctex_status_t synctex_display_query(synctex_scanner_p scanner,const char * name,int line,int column, int page_hint); 203 | synctex_status_t synctex_edit_query(synctex_scanner_p scanner,int page,float h,float v); 204 | synctex_node_p synctex_scanner_next_result(synctex_scanner_p scanner); 205 | synctex_status_t synctex_scanner_reset_result(synctex_scanner_p scanner); 206 | 207 | /** 208 | * The horizontal and vertical location, 209 | * the width, height and depth of a box enclosing node. 210 | * All dimensions are given in page coordinates 211 | * as opposite to TeX coordinates. 212 | * The origin is at the top left corner of the page. 213 | * Code example for Qt5: 214 | * (from TeXworks source TWSynchronize.cpp) 215 | * QRectF nodeRect(synctex_node_box_visible_h(node), 216 | * synctex_node_box_visible_v(node) - 217 | * synctex_node_box_visible_height(node), 218 | * synctex_node_box_visible_width(node), 219 | * synctex_node_box_visible_height(node) + 220 | * synctex_node_box_visible_depth(node)); 221 | * Code example for Cocoa: 222 | * NSRect bounds = [pdfPage 223 | * boundsForBox:kPDFDisplayBoxMediaBox]; 224 | * NSRect nodeRect = NSMakeRect( 225 | * synctex_node_box_visible_h(node), 226 | * NSMaxY(bounds)-synctex_node_box_visible_v(node) + 227 | * synctex_node_box_visible_height(node), 228 | * synctex_node_box_visible_width(node), 229 | * synctex_node_box_visible_height(node) + 230 | * synctex_node_box_visible_depth(node) 231 | * ); 232 | * The visible dimensions are bigger than real ones 233 | * to compensate 0 width boxes or nodes intentionnaly 234 | * put outside the box (using \kern for example). 235 | * - parameter node: a node. 236 | * - returns: a float. 237 | * - author: JL 238 | */ 239 | float synctex_node_box_visible_h(synctex_node_p node); 240 | float synctex_node_box_visible_v(synctex_node_p node); 241 | float synctex_node_box_visible_width(synctex_node_p node); 242 | float synctex_node_box_visible_height(synctex_node_p node); 243 | float synctex_node_box_visible_depth(synctex_node_p node); 244 | 245 | /** 246 | * For quite all nodes, horizontal and vertical coordinates, and width. 247 | * All dimensions are given in page coordinates 248 | * as opposite to TeX coordinates. 249 | * The origin is at the top left corner of the page. 250 | * The visible dimensions are bigger than real ones 251 | * to compensate 0 width boxes or nodes intentionnaly 252 | * put outside the box (using \kern for example). 253 | * All nodes have coordinates, but all nodes don't 254 | * have non null size. For example, math nodes 255 | * have no width according to TeX, and in that case 256 | * synctex_node_visible_width simply returns 0. 257 | * The same holds for kern nodes that do not have 258 | * height nor depth, etc... 259 | */ 260 | float synctex_node_visible_h(synctex_node_p node); 261 | float synctex_node_visible_v(synctex_node_p node); 262 | float synctex_node_visible_width(synctex_node_p node); 263 | float synctex_node_visible_height(synctex_node_p node); 264 | float synctex_node_visible_depth(synctex_node_p node); 265 | 266 | /** 267 | * Given a node, access to its tag, line and column. 268 | * The line and column numbers are 1 based. 269 | * The latter is not yet fully supported in TeX, 270 | * the default implementation returns 0 271 | * which means the whole line. 272 | * synctex_node_get_name returns the path of the 273 | * TeX source file that was used to create the node. 274 | * When the tag is known, the scanner of the node 275 | * will also give that same file name, see 276 | * synctex_scanner_get_name below. 277 | * For an hbox node, the mean line is the mean 278 | * of all the lines of the child nodes. 279 | * Sometimes, when synchronization form pdf to source 280 | * fails with the line, one should try with the 281 | * mean line. 282 | */ 283 | int synctex_node_tag(synctex_node_p node); 284 | int synctex_node_line(synctex_node_p node); 285 | int synctex_node_mean_line(synctex_node_p node); 286 | int synctex_node_column(synctex_node_p node); 287 | const char * synctex_node_get_name(synctex_node_p node); 288 | 289 | /** 290 | This is the page where the node appears. 291 | * This is a 1 based index as given by TeX. 292 | */ 293 | int synctex_node_page(synctex_node_p node); 294 | 295 | /** 296 | * Display all the information contained in the scanner. 297 | * If the records are too numerous, only the first ones are displayed. 298 | * This is mainly for informational purpose to help developers. 299 | */ 300 | void synctex_scanner_display(synctex_scanner_p scanner); 301 | 302 | /* Managing the input file names. 303 | * Given a tag, synctex_scanner_get_name will return the corresponding file name. 304 | * Conversely, given a file name, synctex_scanner_get_tag will return, the corresponding tag. 305 | * The file name must be the very same as understood by TeX. 306 | * For example, if you \input myDir/foo.tex, the file name is myDir/foo.tex. 307 | * No automatic path expansion is performed. 308 | * Finally, synctex_scanner_input is the first input node of the scanner. 309 | * To browse all the input node, use a loop like 310 | * ... 311 | * synctex_node_p = input_node; 312 | * ... 313 | * if((input_node = synctex_scanner_input(scanner))) { 314 | * do { 315 | * blah 316 | * } while((input_node=synctex_node_sibling(input_node))); 317 | * } 318 | * 319 | * The output is the name that was used to create the scanner. 320 | * The synctex is the real name of the synctex file, 321 | * it was obtained from output by setting the proper file extension. 322 | */ 323 | const char * synctex_scanner_get_name(synctex_scanner_p scanner,int tag); 324 | 325 | int synctex_scanner_get_tag(synctex_scanner_p scanner,const char * name); 326 | 327 | synctex_node_p synctex_scanner_input(synctex_scanner_p scanner); 328 | synctex_node_p synctex_scanner_input_with_tag(synctex_scanner_p scanner,int tag); 329 | const char * synctex_scanner_get_output(synctex_scanner_p scanner); 330 | const char * synctex_scanner_get_synctex(synctex_scanner_p scanner); 331 | 332 | /* The x and y offset of the origin in TeX coordinates. The magnification 333 | These are used by pdf viewers that want to display the real box size. 334 | For example, getting the horizontal coordinates of a node would require 335 | synctex_node_box_h(node)*synctex_scanner_magnification(scanner)+synctex_scanner_x_offset(scanner) 336 | Getting its TeX width would simply require 337 | synctex_node_box_width(node)*synctex_scanner_magnification(scanner) 338 | but direct methods are available for that below. 339 | */ 340 | int synctex_scanner_x_offset(synctex_scanner_p scanner); 341 | int synctex_scanner_y_offset(synctex_scanner_p scanner); 342 | float synctex_scanner_magnification(synctex_scanner_p scanner); 343 | 344 | /** 345 | * ## Browsing the nodes 346 | * parent, child and sibling are standard names for tree nodes. 347 | * The parent is one level higher, 348 | * the child is one level deeper, 349 | * and the sibling is at the same level. 350 | * A node and its sibling have the same parent. 351 | * A node is the parent of its children. 352 | * A node is either the child of its parent, 353 | * or belongs to the sibling chain of its parent's child. 354 | * The sheet or form of a node is the topmost ancestor, 355 | * it is of type sheet or form. 356 | * The next node is either the child, the sibling or the parent's sibling, 357 | * unless the parent is a sheet, a form or NULL. 358 | * This allows to navigate through all the nodes of a given sheet node: 359 | * 360 | * synctex_node_p node = sheet; 361 | * while((node = synctex_node_next(node))) { 362 | * // do something with node 363 | * } 364 | * 365 | * With synctex_sheet_content and synctex_form_content, 366 | * you can retrieve the sheet node given the page 367 | * or form tag. 368 | * The page is 1 based, according to TeX standards. 369 | * Conversely synctex_node_parent_sheet or 370 | * synctex_node_parent_form allows to retrieve 371 | * the sheet or the form containing a given node. 372 | * Notice that a node is not contained in a sheet 373 | * and a form at the same time. 374 | * Some nodes are not contained in either (handles). 375 | */ 376 | 377 | synctex_node_p synctex_node_parent(synctex_node_p node); 378 | synctex_node_p synctex_node_parent_sheet(synctex_node_p node); 379 | synctex_node_p synctex_node_parent_form(synctex_node_p node); 380 | synctex_node_p synctex_node_child(synctex_node_p node); 381 | synctex_node_p synctex_node_last_child(synctex_node_p node); 382 | synctex_node_p synctex_node_sibling(synctex_node_p node); 383 | synctex_node_p synctex_node_last_sibling(synctex_node_p node); 384 | synctex_node_p synctex_node_arg_sibling(synctex_node_p node); 385 | synctex_node_p synctex_node_next(synctex_node_p node); 386 | 387 | /** 388 | * Top level entry points. 389 | * The scanner owns a list of sheet siblings and 390 | * a list of form siblings. 391 | * Sheets or forms have one child which is a box: 392 | * theie contents. 393 | * - argument page: 1 based sheet page number. 394 | * - argument tag: 1 based form tag number. 395 | */ 396 | synctex_node_p synctex_sheet(synctex_scanner_p scanner,int page); 397 | synctex_node_p synctex_sheet_content(synctex_scanner_p scanner,int page); 398 | synctex_node_p synctex_form(synctex_scanner_p scanner,int tag); 399 | synctex_node_p synctex_form_content(synctex_scanner_p scanner,int tag); 400 | 401 | /* This is primarily used for debugging purpose. 402 | * The second one logs information for the node and recursively displays information for its next node */ 403 | void synctex_node_log(synctex_node_p node); 404 | void synctex_node_display(synctex_node_p node); 405 | 406 | /* For quite all nodes, horizontal, vertical coordinates, and width. 407 | * These are expressed in TeX small points coordinates, with origin at the top left corner. 408 | */ 409 | int synctex_node_h(synctex_node_p node); 410 | int synctex_node_v(synctex_node_p node); 411 | int synctex_node_width(synctex_node_p node); 412 | int synctex_node_height(synctex_node_p node); 413 | int synctex_node_depth(synctex_node_p node); 414 | 415 | /* For all nodes, dimensions of the enclosing box. 416 | * These are expressed in TeX small points coordinates, with origin at the top left corner. 417 | * A box is enclosing itself. 418 | */ 419 | int synctex_node_box_h(synctex_node_p node); 420 | int synctex_node_box_v(synctex_node_p node); 421 | int synctex_node_box_width(synctex_node_p node); 422 | int synctex_node_box_height(synctex_node_p node); 423 | int synctex_node_box_depth(synctex_node_p node); 424 | 425 | #ifdef __cplusplus 426 | } 427 | #endif 428 | 429 | #endif 430 | -------------------------------------------------------------------------------- /server/synctex_parser_local.h: -------------------------------------------------------------------------------- 1 | #include 2 | #define printf(fmt, args...) (fprintf (stderr, (fmt), ## args)) 3 | #define SYNCTEX_INLINE 4 | -------------------------------------------------------------------------------- /server/synctex_parser_readme.txt: -------------------------------------------------------------------------------- 1 | This file is part of the SyncTeX package. 2 | 3 | Please refer to synctex_parser_readme.md 4 | 5 | The Synchronization TeXnology named SyncTeX is a new feature 6 | of recent TeX engines designed by Jerome Laurens. 7 | It allows to synchronize between input and output, which means to 8 | navigate from the source document to the typeset material and vice versa. 9 | More information on http://itexmac2.sourceforge.net/SyncTeX.html 10 | 11 | This package is mainly for developers, it mainly contains the following files: 12 | 13 | synctex_parser_readme.txt 14 | synctex_parser_version.txt 15 | synctex_parser_utils.c 16 | synctex_parser_utils.h 17 | synctex_parser_local.h 18 | synctex_parser_private.h 19 | synctex_parser.h 20 | synctex_parser.c 21 | 22 | The file you are reading contains more information about the SyncTeX parser history. 23 | 24 | In order to support SyncTeX in a viewer, it is sufficient to include 25 | in the source the files synctex_parser.h and synctex_parser.c. 26 | The synctex parser usage is described in synctex_parser.h header file. 27 | 28 | The other files are used by tex engines or by the synctex command line utility: 29 | 30 | ChangeLog 31 | README.txt 32 | am 33 | man1 34 | man5 35 | synctex-common.h 36 | synctex-convert.sh 37 | synctex-e-mem.ch0 38 | synctex-e-mem.ch1 39 | synctex-e-rec.ch0 40 | synctex-e-rec.ch1 41 | synctex-etex.h 42 | synctex-mem.ch0 43 | synctex-mem.ch1 44 | synctex-mem.ch2 45 | synctex-pdf-rec.ch2 46 | synctex-pdftex.h 47 | synctex-rec.ch0 48 | synctex-rec.ch1 49 | synctex-rec.ch2 50 | synctex-tex.h 51 | synctex-xe-mem.ch2 52 | synctex-xe-rec.ch2 53 | synctex-xe-rec.ch3 54 | synctex-xetex.h 55 | synctex.c 56 | synctex.defines 57 | synctex.h 58 | synctex_main.c 59 | tests 60 | 61 | 62 | Version: 63 | -------- 64 | This is version 1, which refers to the synctex output file format. 65 | The files are identified by a build number. 66 | In order to help developers to automatically manage the version and build numbers 67 | and download the parser only when necessary, the synctex_parser.version 68 | is an ASCII text file just containing the current version and build numbers. 69 | 70 | History: 71 | -------- 72 | 1.1: Thu Jul 17 09:28:13 UTC 2008 73 | - First official version available in TeXLive 2008 DVD. 74 | Unfortunately, the backwards synchronization is not working properly mainly for ConTeXt users, see below. 75 | 1.2: Tue Sep 2 10:28:32 UTC 2008 76 | - Correction for ConTeXt support in the edit query. 77 | The previous method was assuming that TeX boxes do not overlap, 78 | which is reasonable for LaTeX but not for ConTeXt. 79 | This assumption is no longer considered. 80 | 1.3: Fri Sep 5 09:39:57 UTC 2008 81 | - Local variable "read" renamed to "already_read" to avoid conflicts. 82 | - "inline" compiler directive renamed to "SYNCTEX_INLINE" for code support and maintenance 83 | - _synctex_error cannot be inlined due to variable arguments (thanks Christiaan Hofman) 84 | - Correction in the display query, extra boundary nodes are used for a more precise forwards synchronization 85 | 1.4: Fri Sep 12 08:12:34 UTC 2008 86 | - For an unknown reason, the previous version was not the real 1.3 (as used in iTeXMac2 build 747). 87 | As a consequence, a crash was observed. 88 | - Some typos are fixed. 89 | 1.6: Mon Nov 3 20:20:02 UTC 2008 90 | - The bug that prevented synchronization with compressed files on windows has been fixed. 91 | - New interface to allow system specific customization. 92 | - Note that some APIs have changed. 93 | 1.8: Mer 8 jul 2009 11:32:38 UTC 94 | Note that version 1.7 was delivered privately. 95 | - bug fix: synctex was causing a memory leak in pdftex and xetex, thus some processing speed degradation 96 | - bug fix: the synctex command line tool was broken when updating a .synctex file 97 | - enhancement: better accuracy of the synchronization process 98 | - enhancement: the pdf output file and the associated .synctex file no longer need to live in the same directory. 99 | The new -d option of the synctex command line tool manages this situation. 100 | This is handy when using something like tex -output-directory=DIR ... 101 | 1.9: Wed Nov 4 11:52:35 UTC 2009 102 | - Various typo fixed 103 | - OutputDebugString replaced by OutputDebugStringA to deliberately disable unicode preprocessing 104 | - New conditional created because OutputDebugStringA is only available since Windows 2K professional 105 | 1.10: Sun Jan 10 10:12:32 UTC 2010 106 | - Bug fix in synctex_parser.c to solve a synchronization problem with amsmath's gather environment. 107 | Concerns the synctex tool. 108 | 1.11: Sun Jan 17 09:12:31 UTC 2010 109 | - Bug fix in synctex_parser.c, function synctex_node_box_visible_v: 'x' replaced by 'y'. 110 | Only 3rd party tools are concerned. 111 | 1.12: Mon Jul 19 21:52:10 UTC 2010 112 | - Bug fix in synctex_parser.c, function __synctex_open: the io_mode was modified even in case of a non zero return, 113 | causing a void .synctex.gz file to be created even if it was not expected. Reported by Marek Kasik concerning a bug on evince. 114 | 1.13: Fri Mar 11 07:39:12 UTC 2011 115 | - Bug fix in synctex_parser.c, better synchronization as suggested by Jan Sundermeyer (near line 3388). 116 | - Stronger code design in synctex_parser_utils.c, function _synctex_get_name (really neutral behavior). 117 | Only 3rd party tools are concerned. 118 | 1.14: Fri Apr 15 19:10:57 UTC 2011 119 | - taking output_directory into account 120 | - Replaced FOPEN_WBIN_MODE by FOPEN_W_MODE when opening the text version of the .synctex file. 121 | - Merging with LuaTeX's version of synctex.c 122 | 1.15: Fri Jun 10 14:10:17 UTC 2011 123 | This concerns the synctex command line tool and 3rd party developers. 124 | TeX and friends are not concerned by these changes. 125 | - Bug fixed in _synctex_get_io_mode_name, sometimes the wrong mode was returned 126 | - Support for LuaTeX convention of './' file prefixing 127 | 1.16: Tue Jun 14 08:23:30 UTC 2011 128 | This concerns the synctex command line tool and 3rd party developers. 129 | TeX and friends are not concerned by these changes. 130 | - Better forward search (thanks Jose Alliste) 131 | - Support for LuaTeX convention of './' file prefixing now for everyone, not only for Windows 132 | 1.17: Fri Oct 14 08:15:16 UTC 2011 133 | This concerns the synctex command line tool and 3rd party developers. 134 | TeX and friends are not concerned by these changes. 135 | - synctex_parser.c: cosmetic changes to enhance code readability 136 | - Better forward synchronization. 137 | The problem occurs for example with LaTeX \item command. 138 | The fact is that this command creates nodes at parse time but these nodes are used only 139 | after the text material of the \item is displayed on the page. The consequence is that sometimes, 140 | forward synchronization spots an irrelevant point from the point of view of the editing process. 141 | This was due to some very basic filtering policy, where a somehow arbitrary choice was made when 142 | many different possibilities where offered for synchronisation. 143 | Now, forward synchronization prefers nodes inside an hbox with as many acceptable spots as possible. 144 | This is achieved with the notion of mean line and node weight. 145 | - Adding support for the new file naming convention with './' 146 | + function synctex_ignore_leading_dot_slash_in_path replaces synctex_ignore_leading_dot_slash 147 | + function _synctex_is_equivalent_file_name is more permissive 148 | Previously, the function synctex_scanner_get_tag would give an answer only when 149 | the given file name was EXACTLY one of the file names listed in the synctex file. 150 | The we added some changes accepting for example 'foo.tex' instead of './foo.tex'. 151 | Now we have an even looser policy for dealing with file names. 152 | If the given file name does not match exactly one the file names of the synctex file, 153 | then we try to match the base names. If there is only one match of the base names, 154 | then it is taken as a match for the whole names. 155 | The base name is defined as following: 156 | ./foo => foo 157 | /my///.////foo => foo 158 | /foo => /foo 159 | /my//.foo => /my//.foo 160 | 1.17: Tue Mar 13 10:10:03 UTC 2012 161 | - minor changes, no version changes 162 | - syntax man pages are fixed as suggested by M. Shimata 163 | see mail to tex-live@tug.org titled "syntax.5 has many warnings from groff" and "syntax.1 use invalid macro for mdoc" 164 | 1.17: Tue Jan 14 09:55:00 UTC 2014 165 | - fixed a segfault, from Sebastian Ramacher 166 | 1.17: Mon Aug 04 167 | - fixed a memory leak 168 | 1.18: Thu Jun 25 11:36:05 UTC 2015 169 | - nested sheets now fully supported (does it make sense in TeX) 170 | - cosmetic changes: uniform indentation 171 | - suppression of warnings, mainly long/int ones. In short, zlib likes ints when size_t likes longs. 172 | - CLI synctex tool can build out of TeXLive (modulo appropriate options passed to the compiler) 173 | 1.19: Thu Mar 9 21:26:27 UTC 2017 174 | - the nested sheets patch was not a good solution. 175 | It has been moved from the parser to the engine. 176 | See the synctex.c source file for detailed explanations. 177 | - there is a new synctex format specification. 178 | We can see that a .synctex file can contain many times 179 | the same vertical position because many objects belong 180 | to the same line. When the options read -synctex=±2 or more, 181 | a very basic compression algorithm is used: 182 | if synctex is about write the same number then it writes 183 | an = sign instead. This saves approximately 10% of the 184 | synctex output file, either compressed or not. 185 | The new synctex parser has been updated accordingly. 186 | Actual tex frontend won't see any difference with the 187 | TeX engines that include this new feature. 188 | Frontends with the new parser won't see any difference 189 | with the older TeX engines. 190 | Frontends with the new parser will only see a difference 191 | with new TeX engines if -synctex=±2 or more is used. 192 | 193 | Acknowledgments: 194 | ---------------- 195 | The author received useful remarks from the pdfTeX developers, especially Hahn The Thanh, 196 | and significant help from XeTeX developer Jonathan Kew 197 | 198 | Nota Bene: 199 | ---------- 200 | If you include or use a significant part of the synctex package into a software, 201 | I would appreciate to be listed as contributor and see "SyncTeX" highlighted. 202 | 203 | Copyright (c) 2008-2014 jerome DOT laurens AT u-bourgogne DOT fr 204 | 205 | -------------------------------------------------------------------------------- /server/synctex_parser_utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008-2017 jerome DOT laurens AT u-bourgogne DOT fr 3 | 4 | This file is part of the __SyncTeX__ package. 5 | 6 | [//]: # (Latest Revision: Fri Jul 14 16:20:41 UTC 2017) 7 | [//]: # (Version: 1.21) 8 | 9 | See `synctex_parser_readme.md` for more details 10 | 11 | ## License 12 | 13 | Permission is hereby granted, free of charge, to any person 14 | obtaining a copy of this software and associated documentation 15 | files (the "Software"), to deal in the Software without 16 | restriction, including without limitation the rights to use, 17 | copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | copies of the Software, and to permit persons to whom the 19 | Software is furnished to do so, subject to the following 20 | conditions: 21 | 22 | The above copyright notice and this permission notice shall be 23 | included in all copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 27 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 29 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 30 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 31 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 32 | OTHER DEALINGS IN THE SOFTWARE 33 | 34 | Except as contained in this notice, the name of the copyright holder 35 | shall not be used in advertising or otherwise to promote the sale, 36 | use or other dealings in this Software without prior written 37 | authorization from the copyright holder. 38 | 39 | */ 40 | 41 | #ifndef SYNCTEX_PARSER_UTILS_H 42 | #define SYNCTEX_PARSER_UTILS_H 43 | 44 | /* The utilities declared here are subject to conditional implementation. 45 | * All the operating system special stuff goes here. 46 | * The problem mainly comes from file name management: path separator, encoding... 47 | */ 48 | 49 | #include "synctex_version.h" 50 | 51 | typedef int synctex_bool_t; 52 | # define synctex_YES (0==0) 53 | # define synctex_NO (0==1) 54 | 55 | # define synctex_ADD_QUOTES -1 56 | # define synctex_COMPRESS -1 57 | # define synctex_DONT_ADD_QUOTES 0 58 | # define synctex_DONT_COMPRESS 0 59 | 60 | #ifndef __SYNCTEX_PARSER_UTILS__ 61 | # define __SYNCTEX_PARSER_UTILS__ 62 | 63 | #include 64 | 65 | #ifdef __cplusplus 66 | extern "C" { 67 | #endif 68 | 69 | # if defined(_WIN32) || defined(__OS2__) 70 | # define SYNCTEX_CASE_SENSITIVE_PATH 0 71 | # define SYNCTEX_IS_PATH_SEPARATOR(c) ('/' == c || '\\' == c) 72 | # else 73 | # define SYNCTEX_CASE_SENSITIVE_PATH 1 74 | # define SYNCTEX_IS_PATH_SEPARATOR(c) ('/' == c) 75 | # endif 76 | 77 | # if defined(_WIN32) || defined(__OS2__) 78 | # define SYNCTEX_IS_DOT(c) ('.' == c) 79 | # else 80 | # define SYNCTEX_IS_DOT(c) ('.' == c) 81 | # endif 82 | 83 | # if SYNCTEX_CASE_SENSITIVE_PATH 84 | # define SYNCTEX_ARE_PATH_CHARACTERS_EQUAL(left,right) (left != right) 85 | # else 86 | # define SYNCTEX_ARE_PATH_CHARACTERS_EQUAL(left,right) (toupper(left) != toupper(right)) 87 | # endif 88 | 89 | /* This custom malloc functions initializes to 0 the newly allocated memory. 90 | * There is no bzero function on windows. */ 91 | void *_synctex_malloc(size_t size); 92 | 93 | /* To balance _synctex_malloc. 94 | * ptr might be NULL. */ 95 | void _synctex_free(void * ptr); 96 | 97 | /* This is used to log some informational message to the standard error stream. 98 | * On Windows, the stderr stream is not exposed and another method is used. 99 | * The return value is the number of characters printed. */ 100 | int _synctex_error(const char * reason,...); 101 | int _synctex_debug(const char * reason,...); 102 | 103 | /* strip the last extension of the given string, this string is modified! 104 | * This function depends on the OS because the path separator may differ. 105 | * This should be discussed more precisely. */ 106 | void _synctex_strip_last_path_extension(char * string); 107 | 108 | /* Compare two file names, windows is sometimes case insensitive... 109 | * The given strings may differ stricto sensu, but represent the same file name. 110 | * It might not be the real way of doing things. 111 | * The return value is an undefined non 0 value when the two file names are equivalent. 112 | * It is 0 otherwise. */ 113 | synctex_bool_t _synctex_is_equivalent_file_name(const char *lhs, const char *rhs); 114 | 115 | /* Description forthcoming.*/ 116 | synctex_bool_t _synctex_path_is_absolute(const char * name); 117 | 118 | /* Description forthcoming...*/ 119 | const char * _synctex_last_path_component(const char * name); 120 | 121 | /* Description forthcoming...*/ 122 | const char * _synctex_base_name(const char *path); 123 | 124 | /* If the core of the last path component of src is not already enclosed with double quotes ('"') 125 | * and contains a space character (' '), then a new buffer is created, the src is copied and quotes are added. 126 | * In all other cases, no destination buffer is created and the src is not copied. 127 | * 0 on success, which means no error, something non 0 means error, mainly due to memory allocation failure, or bad parameter. 128 | * This is used to fix a bug in the first version of pdftex with synctex (1.40.9) for which names with spaces 129 | * were not managed in a standard way. 130 | * On success, the caller owns the buffer pointed to by dest_ref (is any) and 131 | * is responsible of freeing the memory when done. 132 | * The size argument is the size of the src buffer. On return the dest_ref points to a buffer sized size+2.*/ 133 | int _synctex_copy_with_quoting_last_path_component(const char * src, char ** dest_ref, size_t size); 134 | 135 | /* These are the possible extensions of the synctex file */ 136 | extern const char * synctex_suffix; 137 | extern const char * synctex_suffix_gz; 138 | 139 | typedef unsigned int synctex_io_mode_t; 140 | 141 | typedef enum { 142 | synctex_io_append_mask = 1, 143 | synctex_io_gz_mask = synctex_io_append_mask<<1 144 | } synctex_io_mode_masks_t; 145 | 146 | typedef enum { 147 | synctex_compress_mode_none = 0, 148 | synctex_compress_mode_gz = 1 149 | } synctex_compress_mode_t; 150 | 151 | int _synctex_get_name(const char * output, const char * build_directory, char ** synctex_name_ref, synctex_io_mode_t * io_mode_ref); 152 | 153 | /* returns the correct mode required by fopen and gzopen from the given io_mode */ 154 | const char * _synctex_get_io_mode_name(synctex_io_mode_t io_mode); 155 | 156 | synctex_bool_t synctex_ignore_leading_dot_slash_in_path(const char ** name); 157 | 158 | #ifdef __cplusplus 159 | } 160 | #endif 161 | 162 | #endif 163 | #endif /* SYNCTEX_PARSER_UTILS_H */ 164 | -------------------------------------------------------------------------------- /server/synctex_parser_version.txt: -------------------------------------------------------------------------------- 1 | 1.21 2 | -------------------------------------------------------------------------------- /server/synctex_version.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008-2017 jerome DOT laurens AT u-bourgogne DOT fr 3 | 4 | This file is part of the __SyncTeX__ package. 5 | 6 | [//]: # (Latest Revision: Fri Jul 14 16:20:41 UTC 2017) 7 | [//]: # (Version: 1.21) 8 | 9 | See `synctex_parser_readme.md` for more details 10 | 11 | ## License 12 | 13 | Permission is hereby granted, free of charge, to any person 14 | obtaining a copy of this software and associated documentation 15 | files (the "Software"), to deal in the Software without 16 | restriction, including without limitation the rights to use, 17 | copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | copies of the Software, and to permit persons to whom the 19 | Software is furnished to do so, subject to the following 20 | conditions: 21 | 22 | The above copyright notice and this permission notice shall be 23 | included in all copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 27 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 29 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 30 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 31 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 32 | OTHER DEALINGS IN THE SOFTWARE 33 | 34 | Except as contained in this notice, the name of the copyright holder 35 | shall not be used in advertising or otherwise to promote the sale, 36 | use or other dealings in this Software without prior written 37 | authorization from the copyright holder. 38 | 39 | ## Acknowledgments: 40 | 41 | The author received useful remarks from the __pdfTeX__ developers, especially Hahn The Thanh, 42 | and significant help from __XeTeX__ developer Jonathan Kew. 43 | 44 | ## Nota Bene: 45 | 46 | If you include or use a significant part of the __SyncTeX__ package into a software, 47 | I would appreciate to be listed as contributor and see "__SyncTeX__" highlighted. 48 | */ 49 | 50 | #ifndef __SYNCTEX_VERSION__ 51 | # define __SYNCTEX_VERSION__ 52 | 53 | # define SYNCTEX_VERSION_MAJOR 1 54 | 55 | # define SYNCTEX_VERSION_STRING "1.21" 56 | 57 | # define SYNCTEX_CLI_VERSION_STRING "1.5" 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /server/test/Makefile: -------------------------------------------------------------------------------- 1 | # +-----------------------------------------------------------+ 2 | # * Docker tests for the autobuild script 3 | # +-----------------------------------------------------------+ 4 | 5 | # List of available OS. 6 | DOCKER_OS = $(patsubst %.Dockerfile.in, %, \ 7 | $(notdir $(wildcard docker/templates/*.Dockerfile.in))) 8 | 9 | # Arguments to pass to docker build . 10 | DOCKER_BUILD_ARGS = -q 11 | 12 | # Advice make not to delete these "intermediate" files. 13 | .PRECIOUS: docker/%.Dockerfile docker/.%.build 14 | 15 | .PHONY: all test check docker/build 16 | 17 | all: docker/test 18 | test: docker/test 19 | check: docker/test 20 | 21 | # Create the Dockerfile 22 | docker/%.Dockerfile: docker/templates/%.Dockerfile.in \ 23 | docker/templates/Dockerfile.in 24 | @echo Creating Dockerfile for target $* 25 | cat $^ > $@ 26 | 27 | # Build the Dockerfile 28 | docker/.%.build: docker/%.Dockerfile ../autobuild docker/lib 29 | @echo Building target $* 30 | docker build $(DOCKER_BUILD_ARGS) -t epdfinfo/$* -f $< ../ 31 | touch $@ 32 | 33 | # Run the Dockerfile 34 | docker/%: docker/.%.build 35 | @echo Running tests on target $* 36 | docker run epdfinfo/$* 37 | 38 | # Run all Dockerfiles 39 | docker/test: docker/build $(patsubst %, docker/%, $(DOCKER_OS)) 40 | 41 | # Build all Dockerfiles 42 | docker/build: $(patsubst %, docker/.%.build, $(DOCKER_OS)) 43 | 44 | clean: 45 | rm -f -- docker/.[^.]*.build 46 | rm -f -- docker/*.Dockerfile 47 | 48 | print: 49 | @for os in $(DOCKER_OS); do echo $$os; done | sort 50 | -------------------------------------------------------------------------------- /server/test/docker/.gitignore: -------------------------------------------------------------------------------- 1 | *.Dockerfile 2 | *.build 3 | -------------------------------------------------------------------------------- /server/test/docker/lib/run-tests: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PATH="$(dirname "$0")":$PATH 4 | 5 | set -e 6 | 7 | yes-or-enter | ./autobuild -i /bin 8 | yes-or-enter | ./autobuild -i /usr/bin | \ 9 | grep -q "Skipping package installation (already installed)" 10 | -------------------------------------------------------------------------------- /server/test/docker/lib/yes-or-enter: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Step over prompts from the package-manager. 4 | if [ -f /etc/arch-release ]; then 5 | yes '' 6 | else 7 | yes 8 | fi 9 | 10 | -------------------------------------------------------------------------------- /server/test/docker/templates/Dockerfile.in: -------------------------------------------------------------------------------- 1 | ADD . /epdfinfo 2 | WORKDIR /epdfinfo 3 | RUN make -s distclean || true 4 | CMD ["sh", "./test/docker/lib/run-tests"] 5 | -------------------------------------------------------------------------------- /server/test/docker/templates/arch.Dockerfile.in: -------------------------------------------------------------------------------- 1 | # -*- dockerfile -*- 2 | FROM base/archlinux 3 | RUN pacman -Syu --noconfirm --noprogressbar && \ 4 | pacman -S --noconfirm --noprogressbar poppler-glib base-devel 5 | 6 | 7 | -------------------------------------------------------------------------------- /server/test/docker/templates/centos-7.Dockerfile.in: -------------------------------------------------------------------------------- 1 | # -*- dockerfile -*- 2 | FROM centos:7 3 | RUN yum update -y && yum install -y gcc gcc-c++ poppler-glib-devel 4 | -------------------------------------------------------------------------------- /server/test/docker/templates/debian-8.Dockerfile.in: -------------------------------------------------------------------------------- 1 | # -*- dockerfile -*- 2 | FROM debian:8 3 | RUN apt-get update -y && apt-get install -y gcc g++ libpoppler-glib-dev 4 | 5 | -------------------------------------------------------------------------------- /server/test/docker/templates/debian-9.Dockerfile.in: -------------------------------------------------------------------------------- 1 | # -*- dockerfile -*- 2 | FROM debian:9 3 | RUN apt-get update -y && apt-get install -y gcc g++ libpoppler-glib-dev 4 | 5 | -------------------------------------------------------------------------------- /server/test/docker/templates/fedora-24.Dockerfile.in: -------------------------------------------------------------------------------- 1 | # -*- dockerfile -*- 2 | FROM fedora:24 3 | RUN dnf update -y && dnf install -y gcc gcc-c++ poppler-glib-devel 4 | 5 | 6 | -------------------------------------------------------------------------------- /server/test/docker/templates/fedora-25.Dockerfile.in: -------------------------------------------------------------------------------- 1 | # -*- dockerfile -*- 2 | FROM fedora:25 3 | RUN dnf update -y && dnf install -y gcc gcc-c++ poppler-glib-devel 4 | 5 | 6 | -------------------------------------------------------------------------------- /server/test/docker/templates/fedora-26.Dockerfile.in: -------------------------------------------------------------------------------- 1 | # -*- dockerfile -*- 2 | FROM fedora:26 3 | RUN dnf update -y && dnf install -y gcc gcc-c++ poppler-glib-devel 4 | 5 | 6 | -------------------------------------------------------------------------------- /server/test/docker/templates/gentoo.Dockerfile.in: -------------------------------------------------------------------------------- 1 | # -*- dockerfile -*- 2 | FROM gentoo/stage3-amd64 3 | RUN emerge --sync && emerge sys-devel/gcc app-text/poppler 4 | 5 | 6 | -------------------------------------------------------------------------------- /server/test/docker/templates/ubuntu-14.Dockerfile.in: -------------------------------------------------------------------------------- 1 | # -*- dockerfile -*- 2 | FROM ubuntu:trusty 3 | RUN apt-get update -y && apt-get install -y gcc g++ libpoppler-glib-dev 4 | 5 | -------------------------------------------------------------------------------- /server/test/docker/templates/ubuntu-16.Dockerfile.in: -------------------------------------------------------------------------------- 1 | # -*- dockerfile -*- 2 | FROM ubuntu:xenial 3 | RUN apt-get update -y && apt-get install -y gcc g++ libpoppler-glib-dev 4 | 5 | -------------------------------------------------------------------------------- /server/test/docker/templates/ubuntu-17.Dockerfile.in: -------------------------------------------------------------------------------- 1 | # -*- dockerfile -*- 2 | FROM ubuntu:artful 3 | RUN apt-get update -y && apt-get install -y gcc g++ libpoppler-glib-dev 4 | 5 | -------------------------------------------------------------------------------- /test/encrypted.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/politza/pdf-tools/acbe49a44a8c3501171a3693fa1934eef40c869e/test/encrypted.pdf -------------------------------------------------------------------------------- /test/pdf-cache-test.el: -------------------------------------------------------------------------------- 1 | 2 | 3 | ;; * ================================================================== * 4 | ;; * Tests for pdf-cache.el 5 | ;; * ================================================================== * 6 | 7 | (require 'pdf-cache) 8 | (require 'ert) 9 | 10 | (ert-deftest pdf-cache-get-image () 11 | (let (pdf-cache--image-cache) 12 | (should-not (pdf-cache-get-image 1 1)) 13 | (setq pdf-cache--image-cache 14 | (list 15 | (pdf-cache--make-image 1 1 "1" nil) 16 | (pdf-cache--make-image 2 1 "2" nil) 17 | (pdf-cache--make-image 3 1 "3" nil))) 18 | (should (equal (pdf-cache-get-image 1 1) "1")) 19 | (should (equal pdf-cache--image-cache 20 | (list 21 | (pdf-cache--make-image 1 1 "1" nil) 22 | (pdf-cache--make-image 2 1 "2" nil) 23 | (pdf-cache--make-image 3 1 "3" nil)))) 24 | (should (equal (pdf-cache-get-image 2 1) "2")) 25 | (should (equal pdf-cache--image-cache 26 | (list 27 | (pdf-cache--make-image 2 1 "2" nil) 28 | (pdf-cache--make-image 1 1 "1" nil) 29 | (pdf-cache--make-image 3 1 "3" nil)))) 30 | (should-not (pdf-cache-get-image 4 1)))) 31 | 32 | -------------------------------------------------------------------------------- /test/pdf-info-test.el: -------------------------------------------------------------------------------- 1 | ;; -*- lexical-binding: t -*- 2 | 3 | (ert-deftest pdf-info-open/close () 4 | (pdf-test-with-test-pdf 5 | (should-not (pdf-info-open)) 6 | (should (pdf-info-close))) 7 | (pdf-test-with-encrypted-pdf 8 | (should-error (pdf-info-open nil "Invalid Password")) 9 | (should-not (pdf-info-open nil "pdftool")))) 10 | 11 | (ert-deftest pdf-info-metadata () 12 | (pdf-test-with-test-pdf 13 | (should (cl-every (lambda (elt) 14 | (and (consp elt) 15 | (symbolp (car elt)))) 16 | (pdf-info-metadata))))) 17 | 18 | (ert-deftest pdf-info-search-string () 19 | (pdf-test-with-test-pdf 20 | (let (matches) 21 | (should (setq matches (pdf-info-search-string "PDF Tools"))) 22 | (should (= 2 (length matches))) 23 | (should (cl-every (lambda (m) 24 | (let-alist m 25 | (and (stringp .text) 26 | (cl-every 'pdf-test-relative-edges-p .edges) 27 | (= 1 .page)))) 28 | matches))))) 29 | 30 | 31 | (ert-deftest pdf-info-search-regexp () 32 | (pdf-test-with-test-pdf 33 | (let (case-fold-search matches) 34 | (should (setq matches (pdf-info-search-regexp "PDF Tools"))) 35 | (should (= 2 (length matches))) 36 | (should (cl-every (lambda (m) 37 | (let-alist m 38 | (and (stringp .text) 39 | (cl-every 'pdf-test-relative-edges-p .edges) 40 | (= 1 .page)))) 41 | matches))))) 42 | 43 | (ert-deftest pdf-info-pagelinks () 44 | (pdf-test-with-test-pdf 45 | (let ((links (pdf-info-pagelinks 3))) 46 | (should (= 2 (length links))) 47 | (should (cl-every 'pdf-test-relative-edges-p 48 | (mapcar (apply-partially 'alist-get 'edges) 49 | links))) 50 | (should (equal (mapcar (apply-partially 'alist-get 'type) links) 51 | '(goto-dest uri))) 52 | (should (equal (mapcar (apply-partially 'alist-get 'uri) 53 | links) 54 | '(nil "http://www.gnu.org"))) 55 | (should (equal (mapcar (apply-partially 'alist-get 'page) 56 | links) 57 | '(1 nil)))))) 58 | 59 | (ert-deftest pdf-info-number-of-pages () 60 | (pdf-test-with-test-pdf 61 | (should (= 6 (pdf-info-number-of-pages))))) 62 | 63 | (ert-deftest pdf-info-outline () 64 | (pdf-test-with-test-pdf 65 | (let ((outline (pdf-info-outline))) 66 | (should (= 7 (length outline))) 67 | (should (equal (mapcar (apply-partially 'alist-get 'depth) outline) 68 | '(1 1 1 1 1 2 3))) 69 | (should (cl-every (lambda (elt) 70 | (eq (alist-get 'type elt) 71 | 'goto-dest)) 72 | outline))))) 73 | 74 | (ert-deftest pdf-info-gettext () 75 | (pdf-test-with-test-pdf 76 | (should (string-match "PDF Tools\\(?:.\\|\n\\)*in memory" 77 | (pdf-info-gettext 1 '(0 0 1 1)))))) 78 | 79 | (ert-deftest pdf-info-getselection () 80 | (pdf-test-with-test-pdf 81 | (should (consp (pdf-info-getselection 1 '(0 0 1 1)))) 82 | (should (cl-every 'pdf-test-relative-edges-p 83 | (pdf-info-getselection 1 '(0 0 1 1)))))) 84 | 85 | (ert-deftest pdf-info-textregions () 86 | (pdf-test-with-test-pdf 87 | (should (consp (pdf-info-textregions 1))))) 88 | 89 | (ert-deftest pdf-info-pagesize () 90 | (pdf-test-with-test-pdf 91 | (should (cl-every (lambda (size) 92 | (and (consp size) 93 | (natnump (car size)) 94 | (natnump (cdr size)))) 95 | (list (pdf-info-pagesize 1)))))) 96 | 97 | (ert-deftest pdf-info-quit () 98 | (pdf-test-with-test-pdf 99 | (pdf-info-quit) 100 | (let (pdf-info-restart-process-p) 101 | (should-error (pdf-info-open))) 102 | (let ((pdf-info-restart-process-p t)) 103 | (pdf-info-open) 104 | (should (pdf-info-close))))) 105 | 106 | (ert-deftest pdf-info-getannots () 107 | (skip-unless (pdf-info-writable-annotations-p)) 108 | (pdf-test-with-test-pdf 109 | (when (memq 'markup-annotations 110 | (pdf-info-features)) 111 | (cl-labels ((alists-get (alists key) 112 | (mapcar (lambda (alist) 113 | (cdr (assoc key alist))) 114 | alists))) 115 | (let (annots) 116 | (should (= 5 (length (setq annots (cl-remove-if 117 | (lambda (elt) 118 | (memq (cdr (assq 'type elt)) 119 | '(file link))) 120 | (pdf-info-getannots)))))) 121 | (should (equal (alists-get annots 'page) 122 | '(2 2 2 2 2))) 123 | (should (equal (sort (copy-sequence (alists-get annots 'type)) 'string-lessp) 124 | (sort (copy-sequence 125 | '(text strike-out highlight underline squiggly)) 126 | 'string-lessp))) 127 | (should (equal (alists-get annots 'color) 128 | '("#ff0000" "#ff0000" "#ff0000" 129 | "#ff0000" "#ff0000"))) 130 | (should (cl-every 'pdf-test-relative-edges-p 131 | (alists-get annots 'edges)))))))) 132 | 133 | (ert-deftest pdf-info-getannot () 134 | (skip-unless (pdf-info-writable-annotations-p)) 135 | (pdf-test-with-test-pdf 136 | (let* ((text-annot (car 137 | (cl-remove-if-not (lambda (elt) 138 | (eq (cdr (assq 'type elt)) 'text)) 139 | (pdf-info-getannots)))) 140 | (key (cdr (assq 'id text-annot)))) 141 | (should (consp text-annot)) 142 | (should (symbolp key)) 143 | (should key) 144 | (should (equal (cl-sort text-annot 'string-lessp :key 'car) 145 | (cl-sort (pdf-info-getannot key) 146 | 'string-lessp :key 'car)))))) 147 | 148 | (ert-deftest pdf-info-addannot () 149 | (skip-unless (pdf-info-writable-annotations-p)) 150 | (pdf-test-with-test-pdf 151 | (let (annot) 152 | (should (consp (setq annot 153 | (pdf-info-addannot 1 '(0 0 1 1) 'text)))) 154 | (should (eq 1 (cdr (assq 'page annot)))) 155 | (should (eq 'text (cdr (assq 'type annot)))) 156 | (should (equal "" (cdr (assq 'contents annot))))))) 157 | 158 | (ert-deftest pdf-info-delannot () 159 | (skip-unless (pdf-info-writable-annotations-p)) 160 | (pdf-test-with-test-pdf 161 | (let ((nannots (length (pdf-info-getannots))) 162 | annots) 163 | (push (pdf-info-addannot 1 '(0 0 1 1) 'text) 164 | annots) 165 | (when (memq 'markup-annotations 166 | (pdf-info-features)) 167 | (push (pdf-info-addannot 1 '(0 0 1 1) 'squiggly '(0 0 1 1)) annots) 168 | (push (pdf-info-addannot 1 '(0 0 1 1) 'highlight '(0 0 1 1)) annots) 169 | (push (pdf-info-addannot 1 '(0 0 1 1) 'underline '(0 0 1 1)) annots) 170 | (push (pdf-info-addannot 1 '(0 0 1 1) 'strike-out '(0 0 1 1)) annots)) 171 | (dolist (a annots) 172 | (pdf-info-delannot (cdr (assq 'id a)))) 173 | (should (= nannots (length (pdf-info-getannots))))))) 174 | 175 | (ert-deftest pdf-info-mvannot () 176 | (skip-unless (pdf-info-writable-annotations-p)) 177 | (pdf-test-with-test-pdf 178 | (let ((edges '(0.25 0.25 1.0 1.0)) 179 | (id (cdr (assq 'id (car (pdf-info-getannots)))))) 180 | (pdf-info-mvannot id edges) 181 | (should (equal (cdr (assq 'edges (pdf-info-getannot id))) 182 | edges))))) 183 | 184 | (ert-deftest pdf-info-editannot () 185 | (skip-unless (pdf-info-writable-annotations-p)) 186 | (pdf-test-with-test-pdf 187 | (let ((color "#ffa500") 188 | (id (cdr (assq 'id (car (pdf-info-getannots)))))) 189 | (should (and id (symbolp id))) 190 | (pdf-info-editannot id `((color . ,color))) 191 | (should (equal (cdr (assq 'color (pdf-info-getannot id))) 192 | color))))) 193 | 194 | (ert-deftest pdf-info-save () 195 | (skip-unless (pdf-info-writable-annotations-p)) 196 | (pdf-test-with-test-pdf 197 | (dolist (id (mapcar (lambda (a) 198 | (cdr (assq 'id a))) 199 | (pdf-info-getannots))) 200 | (pdf-info-delannot id)) 201 | (let (tempfile) 202 | (unwind-protect 203 | (progn 204 | (setq tempfile (pdf-info-save)) 205 | (should (file-exists-p tempfile)) 206 | (should (= 0 (length (pdf-info-getannots nil tempfile))))) 207 | (when (file-exists-p tempfile) 208 | (delete-file tempfile)))))) 209 | 210 | (ert-deftest pdf-info-check-epdfinfo () 211 | (should (progn (pdf-info-check-epdfinfo) t)) 212 | (should-error 213 | (let (pdf-info-epdfinfo-program) 214 | (pdf-info-check-epdfinfo))) 215 | (should-error 216 | (let ((pdf-info-epdfinfo-program 42)) 217 | (pdf-info-check-epdfinfo))) 218 | (should-error 219 | (let ((pdf-info-epdfinfo-program 220 | (make-temp-name "pdf-info"))) 221 | (pdf-info-check-epdfinfo)))) 222 | 223 | ;; FIXME: Write me. 224 | ;; (ert-deftest pdf-info-getattachment-from-annot () 225 | ;; (pdf-test-with-test-pdf 226 | ;; )) 227 | 228 | ;; (ert-deftest pdf-info-getattachments () 229 | ;; (pdf-test-with-test-pdf 230 | ;; )) 231 | 232 | ;; (ert-deftest pdf-info-synctex-forward-search () 233 | ;; (pdf-test-with-test-pdf 234 | ;; )) 235 | 236 | ;; (ert-deftest pdf-info-synctex-backward-search () 237 | ;; (pdf-test-with-test-pdf 238 | ;; )) 239 | 240 | ;; (ert-deftest pdf-info-renderpage () 241 | ;; (pdf-test-with-test-pdf 242 | ;; )) 243 | 244 | ;; (ert-deftest pdf-info-renderpage-text-regions () 245 | ;; (pdf-test-with-test-pdf 246 | ;; )) 247 | 248 | ;; (ert-deftest pdf-info-renderpage-highlight () 249 | ;; (pdf-test-with-test-pdf 250 | ;; )) 251 | 252 | ;; (ert-deftest pdf-info-boundingbox () 253 | ;; (pdf-test-with-test-pdf 254 | ;; )) 255 | -------------------------------------------------------------------------------- /test/pdf-loader-test.el: -------------------------------------------------------------------------------- 1 | ;; -*- lexical-binding: t -*- 2 | 3 | (require 'pdf-loader) 4 | (require 'ert) 5 | (require 'cl-lib) 6 | 7 | (ert-deftest pdf-loader-activation () 8 | :expected-result :failed ;; Until someone figures out how to run the 9 | ;; tests w/o loading all of the package. 10 | (should-not (memq 'pdf-tools features)) 11 | (pdf-loader-install) 12 | (with-current-buffer (find-file "test.pdf") 13 | (should (eq major-mode 'pdf-view-mode)))) 14 | 15 | (ert-deftest pdf-loader-install/uninstall-alists () 16 | (cl-labels ((alists-installed-p () 17 | (and (assoc pdf-loader--auto-mode-alist-item 18 | auto-mode-alist) 19 | (assoc pdf-loader--magic-mode-alist-item 20 | magic-mode-alist)))) 21 | (pdf-loader--install #'ignore) 22 | (should (alists-installed-p)) 23 | (pdf-loader--uninstall) 24 | (should-not (alists-installed-p)))) 25 | -------------------------------------------------------------------------------- /test/pdf-sync-test.el: -------------------------------------------------------------------------------- 1 | 2 | 3 | ;; * ================================================================== * 4 | ;; * Tests for pdf-sync.el 5 | ;; * ================================================================== * 6 | 7 | (require 'ert) 8 | (require 'pdf-sync) 9 | 10 | (ert-deftest pdf-sync-backward--source-strip-comments () 11 | (should-not (cl-remove-if 12 | (lambda (ch) 13 | (memq ch '(?\\ ?\s ?\n))) 14 | (append (pdf-sync-backward--source-strip-comments 15 | (concat "%comment\n" 16 | " %comment\n" 17 | "\\\\%comment\n" 18 | "\\\\ %comment\n" 19 | " \\\\ %comment\n" 20 | " \\\\%comment\n")) 21 | nil))) 22 | (let ((source (concat "\\%comment\n" 23 | " \\%comment\n" 24 | "\\ %comment\n" 25 | " \\ %comment\n" 26 | " \\%comment\n"))) 27 | (should (equal (pdf-sync-backward--source-strip-comments source) 28 | source)))) 29 | 30 | (ert-deftest pdf-sync-backward--get-text-context () 31 | (pdf-test-with-test-pdf 32 | (should (= 3 (length (pdf-sync-backward--get-text-context 1 0.5 0.25)))) 33 | ;; Empty page 34 | (should (equal '(-1 0 nil) 35 | (pdf-sync-backward--get-text-context 6 0.5 0.5))))) 36 | 37 | (ert-deftest pdf-sync-backward--get-source-context () 38 | (with-temp-buffer 39 | (should-not (pdf-sync-backward--get-source-context)) 40 | (insert "\\begin{foo}\nsource\n\\end{foo}") 41 | (should (cl-every 'stringp (pdf-sync-backward--get-source-context))))) 42 | 43 | (ert-deftest pdf-sync-backward--find-position () 44 | (let ((context '(3 2 ("000" "1111" "222222" "333333" "4444" "55555"))) 45 | (prefix "000 XXXX 222222 YYYY 333") 46 | (suffix "333 4444 XXXX")) 47 | (with-temp-buffer 48 | (insert prefix suffix) 49 | (should (progn (pdf-sync-backward--find-position 1 -1 context) 50 | (eq (length prefix) (point))))))) 51 | -------------------------------------------------------------------------------- /test/pdf-tools-test.el: -------------------------------------------------------------------------------- 1 | 2 | 3 | ;; * ================================================================== * 4 | ;; * Tests not fitting anywhere else 5 | ;; * ================================================================== * 6 | 7 | (require 'ert) 8 | 9 | (ert-deftest pdf-tools-semantic-workaround () 10 | (let (python-mode-hook) 11 | (require 'tablist) 12 | (should (null python-mode-hook)))) 13 | -------------------------------------------------------------------------------- /test/pdf-util-test.el: -------------------------------------------------------------------------------- 1 | 2 | 3 | ;; * ================================================================== * 4 | ;; * Tests for pdf-util.el 5 | ;; * ================================================================== * 6 | 7 | (require 'ert) 8 | 9 | (ert-deftest pdf-util-seq-alignment () 10 | (let ((s1 '(?a ?b ?c)) 11 | (s2 '(?a ?b ?c ?d)) 12 | (s3 '(-1 ?a ?b ?c)) 13 | (s4 '(?e ?f ?g)) 14 | (s5 '(?A ?B ?C)) 15 | (s6 '(?b))) 16 | (should (equal '(2 . ((?a . ?a) (?b . ?b) (?c . ?c) (nil . ?d))) 17 | (pdf-util-seq-alignment s1 s2))) 18 | 19 | (should (equal '(3 . ((?a . ?a) (?b . ?b) (?c . ?c) (nil . ?d))) 20 | (pdf-util-seq-alignment s1 s2 nil 'prefix))) 21 | 22 | (should (equal '(3 . ((nil . -1) (?a . ?a) (?b . ?b) (?c . ?c))) 23 | (pdf-util-seq-alignment s1 s3 nil 'suffix))) 24 | 25 | (should (equal '(3 . ((nil . -1) (?a . ?a) (?b . ?b) (?c . ?c))) 26 | (pdf-util-seq-alignment s1 s3 nil 'infix))) 27 | 28 | (should (equal '(1 . ((nil . ?a) (?b . ?b) (nil . ?c))) 29 | (pdf-util-seq-alignment s6 s1 nil 'infix))) 30 | 31 | (should (equal '(-3 . ((?a . ?e) (?b . ?f) (?c . ?g))) 32 | (pdf-util-seq-alignment s1 s4 nil))) 33 | 34 | (should (equal '(3 . ((?a . ?e) (?b . ?f) (?c . ?g))) 35 | (pdf-util-seq-alignment 36 | s1 s4 (lambda (a b) 1)))) 37 | (should (equal '(3 . ((?A . ?a) (?B . ?b) (?C . ?c))) 38 | (pdf-util-seq-alignment 39 | s5 s1 (lambda (a b) 40 | (if (equal (downcase a) 41 | (downcase b)) 42 | 1 43 | -1))))))) 44 | -------------------------------------------------------------------------------- /test/pdf-view-test.el: -------------------------------------------------------------------------------- 1 | ;; -*- lexical-binding: t -*- 2 | 3 | (require 'pdf-view) 4 | (require 'ert) 5 | 6 | (ert-deftest pdf-view-handle-archived-file () 7 | :expected-result :failed 8 | (skip-unless (executable-find "gzip")) 9 | (let ((tramp-verbose 0) 10 | (temp 11 | (make-temp-file "pdf-test"))) 12 | (unwind-protect 13 | (progn 14 | (copy-file "test.pdf" temp t) 15 | (call-process "gzip" nil nil nil temp) 16 | (setq temp (concat temp ".gz")) 17 | (should (numberp (pdf-info-number-of-pages temp))))) 18 | (when (file-exists-p temp) 19 | (delete-file temp)))) 20 | -------------------------------------------------------------------------------- /test/pdf-virtual-test.el: -------------------------------------------------------------------------------- 1 | ;; -*- lexical-binding: t -*- 2 | 3 | (unless (version< emacs-version "24.4") 4 | (require 'pdf-virtual) 5 | (require 'ert) 6 | 7 | (defvar pdf-virtual-test-document 8 | '(("test.pdf" 9 | ;; Annotations 3,4,5 10 | (2 . (0.1805 0.2462 0.4046 0.3392)) 11 | ;; Should match first paragraph. 12 | (1 . (0.2163 0.1879 0.7848 0.22)) 13 | 4 3 5 6))) 14 | 15 | (defmacro with-pdf-virtual-test-document (var &rest body) 16 | (declare (indent 1) (debug t)) 17 | `(let ((,var (pdf-virtual-document-create 18 | pdf-virtual-test-document))) 19 | ,@body)) 20 | 21 | (defmacro with-pdf-virtual-test-buffer (&rest body) 22 | (declare (indent 0) (debug t)) 23 | `(let ((doc pdf-virtual-test-document)) 24 | (pdf-info-process-assert-running t) 25 | (with-temp-buffer 26 | (insert ";; %VPDF 1.0\n\n") 27 | (let (print-length) 28 | (pp doc (current-buffer))) 29 | (pdf-virtual-view-mode) 30 | (progn ,@body)))) 31 | 32 | (ert-deftest pdf-virtual-document-create () 33 | (let ((doc (pdf-virtual-document-create 34 | pdf-virtual-test-document))) 35 | (should (pdf-virtual-document-p doc)) 36 | (should (= (length (pdf-virtual-document-page-array doc)) 37 | 6)) 38 | (should (equal (pdf-virtual-document-file-map doc) 39 | '(("test.pdf" 1)))))) 40 | 41 | (ert-deftest pdf-virtual-document-filenames () 42 | (with-pdf-virtual-test-document doc 43 | (should (equal (pdf-virtual-document-filenames doc) 44 | '("test.pdf"))))) 45 | 46 | (ert-deftest pdf-virtual-document-pages () 47 | (with-pdf-virtual-test-document doc 48 | (should (equal '(("test.pdf" (4 . 4) nil) 49 | ("test.pdf" (3 . 3) nil) 50 | ("test.pdf" (5 . 6) nil)) 51 | (pdf-virtual-document-pages '(3 . 6) doc))))) 52 | 53 | (ert-deftest pdf-virtual-document-page () 54 | (with-pdf-virtual-test-document doc 55 | (should (equal '("test.pdf" 6 nil) 56 | (pdf-virtual-document-page 6 doc))))) 57 | 58 | (ert-deftest pdf-virtual-document-page-of () 59 | (with-pdf-virtual-test-document doc 60 | (let ((pages '(2 1 4 3 5 6))) 61 | (dotimes (i (length pages)) 62 | (should (equal (1+ i) 63 | (pdf-virtual-document-page-of 64 | "test.pdf" (nth i pages) nil doc))))))) 65 | 66 | (ert-deftest pdf-virtual-open () 67 | (with-pdf-virtual-test-buffer 68 | (should (progn (pdf-info-open) t)))) 69 | 70 | (ert-deftest pdf-virtual-close () 71 | (with-pdf-virtual-test-buffer 72 | (should (progn (pdf-info-close) t)))) 73 | 74 | (ert-deftest pdf-virtual-metadata () 75 | (with-pdf-virtual-test-buffer 76 | (should (consp (pdf-info-metadata))))) 77 | 78 | (ert-deftest pdf-virtual-search () 79 | (with-pdf-virtual-test-buffer 80 | (dolist (m (list (pdf-info-search-string "PDF" 2) 81 | (pdf-info-search-regexp "PDF" 2))) 82 | (should (= 2 (length m))) 83 | (should (equal (mapcar (apply-partially 'alist-get 'page) 84 | m) 85 | '(2 2))) 86 | (should (cl-every (lambda (elt) 87 | (cl-every 'pdf-test-relative-edges-p elt)) 88 | (mapcar (apply-partially 'alist-get 'edges) 89 | m)))))) 90 | 91 | (ert-deftest pdf-virtual-pagelinks () 92 | (with-pdf-virtual-test-buffer 93 | (let ((links (pdf-info-pagelinks 4))) 94 | (should (cl-every 'pdf-test-relative-edges-p 95 | (mapcar (apply-partially 'alist-get 'edges) 96 | links))) 97 | (should (equal (mapcar (apply-partially 'alist-get 'type) 98 | links) 99 | '(goto-dest uri))) 100 | (should (equal (mapcar (apply-partially 'alist-get 'uri) 101 | links) 102 | '(nil "http://www.gnu.org")))))) 103 | 104 | (ert-deftest pdf-virtual-number-of-pages () 105 | (with-pdf-virtual-test-buffer 106 | (should (= 6 (pdf-info-number-of-pages))))) 107 | 108 | (ert-deftest pdf-virtual-outline () 109 | (with-pdf-virtual-test-buffer 110 | (let ((outline (pdf-info-outline))) 111 | (should (= 8 (length outline))) 112 | (should (equal (mapcar (apply-partially 'alist-get 'depth) 113 | outline) 114 | '(1 2 2 2 2 2 3 4))) 115 | (should (cl-every (lambda (type) 116 | (equal type 'goto-dest)) 117 | (mapcar (apply-partially 'alist-get 'type) 118 | (cdr outline))))))) 119 | 120 | (ert-deftest pdf-virtual-gettext () 121 | (with-pdf-virtual-test-buffer 122 | (let ((text (pdf-info-gettext 2 '(0 0 1 1)))) 123 | (should 124 | (= 2 (with-temp-buffer 125 | (insert text) 126 | (count-matches "PDF" 1 (point)))))))) 127 | 128 | (ert-deftest pdf-virtual-getselection () 129 | (with-pdf-virtual-test-buffer 130 | (should (consp (pdf-info-getselection 1 '(0 0 1 1)))) 131 | (should (cl-every 'pdf-test-relative-edges-p 132 | (pdf-info-getselection 1 '(0 0 1 1)))))) 133 | 134 | (ert-deftest pdf-virtual-charlayout () 135 | (with-pdf-virtual-test-buffer 136 | (let ((cl (pdf-info-charlayout 1))) 137 | (should (eq ?3 (car (car cl)))) 138 | (should (eq ?y (car (car (last cl))))) 139 | (should (cl-every 'characterp (mapcar 'car cl))) 140 | (should (cl-every 141 | (apply-partially 142 | 'cl-every 'pdf-test-relative-edges-p) 143 | (mapcar 'cdr cl)))))) 144 | 145 | (ert-deftest pdf-virtual-pagesize () 146 | (with-pdf-virtual-test-buffer 147 | (let* ((os '(612 . 792)) 148 | (s (pdf-info-pagesize 1)) 149 | (ds (cons (* (- 0.4046 0.1879) (car os)) 150 | (* (- 0.3392 0.2462) (cdr os))))) 151 | (should (< (abs (- (car s) (car ds))) 10)) 152 | (should (< (abs (- (cdr s) (cdr ds))) 10))))) 153 | 154 | (ert-deftest pdf-virtual-getannots () 155 | (with-pdf-virtual-test-buffer 156 | (let ((a (pdf-info-getannots 1))) 157 | (should (= 3 (length a))) 158 | (should (equal (sort (copy-sequence '(highlight underline squiggly)) 159 | 'string<) 160 | (sort (mapcar (lambda (elt) 161 | (cdr (assq 'type elt))) 162 | a) 163 | 'string<)))))) 164 | 165 | (ert-deftest pdf-virtual-getannot () 166 | (with-pdf-virtual-test-buffer 167 | (let* ((a1 (car (pdf-info-getannots 1))) 168 | (a2 (pdf-info-getannot (cdr (assq 'id a1))))) 169 | (should (equal a1 a2))))) 170 | 171 | (ert-deftest pdf-virtual-addannot () 172 | (with-pdf-virtual-test-buffer 173 | (should-error (pdf-info-addannot 1 '(0 0 1 1) 'text)))) 174 | 175 | (ert-deftest pdf-virtual-delannot () 176 | (skip-unless (pdf-info-writable-annotations-p)) 177 | (with-pdf-virtual-test-buffer 178 | (should-error (pdf-info-delannot 179 | (cdr (assq 'id (car (pdf-info-getannots 1)))))))) 180 | 181 | (ert-deftest pdf-virtual-mvannot () 182 | (skip-unless (pdf-info-writable-annotations-p)) 183 | (with-pdf-virtual-test-buffer 184 | (should-error (pdf-info-mvannot 185 | (cdr (assq 'id (car (pdf-info-getannots 1)))) 186 | '(0 0 0 0))))) 187 | 188 | (ert-deftest pdf-virtual-editannot () 189 | (skip-unless (pdf-info-writable-annotations-p)) 190 | (with-pdf-virtual-test-buffer 191 | (should-error (pdf-info-editannot 192 | (cdr (assq 'id (car (pdf-info-getannots 1)))) 193 | '((color . "blue")))))) 194 | 195 | (ert-deftest pdf-virtual-save () 196 | (skip-unless (pdf-info-writable-annotations-p)) 197 | (with-pdf-virtual-test-buffer 198 | (should-error (pdf-info-save)))) 199 | 200 | (ert-deftest pdf-virtual-adapter-argument-handling () 201 | (let ((enabled-p pdf-virtual-global-minor-mode)) 202 | (unwind-protect 203 | (progn 204 | (pdf-virtual-global-minor-mode 1) 205 | (with-pdf-virtual-test-buffer 206 | (should (stringp (pdf-info-renderpage 1 100 :alpha 0.1))) 207 | (should (stringp (pdf-info-renderpage 208 | 1 100 (current-buffer) :alpha 0.2)))) 209 | (pdf-test-with-test-pdf 210 | (should (plist-get (pdf-info-setoptions 211 | :render/printed t) 212 | :render/printed)) 213 | (should-not (plist-get (pdf-info-setoptions 214 | (current-buffer) 215 | :render/printed nil) 216 | :render/printed)) 217 | (should (plist-get (pdf-info-setoptions 218 | (buffer-file-name) 219 | :render/printed t) 220 | :render/printed)))) 221 | (unless enabled-p 222 | (pdf-virtual-global-minor-mode -1))))) 223 | 224 | ;; (ert-deftest pdf-virtual-getattachment-from-annot () 225 | ;; ) 226 | 227 | ;; (ert-deftest pdf-virtual-getattachments () 228 | ;; ) 229 | 230 | ;; (ert-deftest pdf-virtual-synctex-forward-search () 231 | ;; ) 232 | 233 | ;; (ert-deftest pdf-virtual-synctex-backward-search () 234 | ;; ) 235 | 236 | ;; (ert-deftest pdf-virtual-renderpage () 237 | ;; ) 238 | 239 | ;; (ert-deftest pdf-virtual-boundingbox () 240 | ;; ) 241 | 242 | ;; (ert-deftest pdf-virtual-pagelabels () 243 | ;; ) 244 | 245 | ;; (ert-deftest pdf-virtual-setoptions () 246 | ;; ) 247 | 248 | ;; (ert-deftest pdf-virtual-getoptions () 249 | ;; ) 250 | 251 | ) 252 | -------------------------------------------------------------------------------- /test/test-helper.el: -------------------------------------------------------------------------------- 1 | ;;; test-helper --- Test helper for pdf-tools 2 | 3 | ;;; Commentary: 4 | ;; test helper inspired from https://github.com/tonini/overseer.el/blob/master/test/test-helper.el 5 | 6 | ;;; Code: 7 | 8 | (require 'package) 9 | (require 'ert) 10 | (require 'cl-lib) 11 | 12 | (unless (getenv "PACKAGE_TAR") 13 | (error "Missing package tar. Must be passed by PACKAGE_TAR env variable")) 14 | 15 | (defvar pdf-tools-package (expand-file-name (getenv "PACKAGE_TAR"))) 16 | 17 | (unless (and (file-exists-p pdf-tools-package) 18 | (string-match "\\.tar\\'" pdf-tools-package)) 19 | (error "Invalid tar package:" pdf-tools-package)) 20 | 21 | (unless load-file-name 22 | (error "load-file-name is unset")) 23 | 24 | (cd (file-name-directory load-file-name)) 25 | (setq package-user-dir (expand-file-name "elpa" (make-temp-file "package" t))) 26 | 27 | (defvar cask-elpa 28 | (cl-labels ((directory-if-exists-p (directory) 29 | (and (file-directory-p directory) 30 | directory))) 31 | (or (directory-if-exists-p 32 | (format "../.cask/%s/elpa" emacs-version)) 33 | (directory-if-exists-p 34 | (format "../.cask/%d.%d/elpa" 35 | emacs-major-version emacs-minor-version)) 36 | (error "Do `cask install' first")))) 37 | 38 | (add-to-list 'package-directory-list cask-elpa) 39 | (add-hook 'kill-emacs-hook (lambda nil 40 | (when (file-exists-p package-user-dir) 41 | (delete-directory package-user-dir t)))) 42 | (package-initialize) 43 | (package-install-file pdf-tools-package) 44 | 45 | ;; FIXME: Move functions to new, loadable file. 46 | ;; Fake skipped as accepted failures if skip-unless is not available. 47 | (unless (fboundp 'ert--skip-unless) 48 | (defun skip-unless (arg) 49 | (unless arg 50 | (setf (ert-test-expected-result-type 51 | (car ert--running-tests)) 52 | :failed) 53 | (ert-fail (list nil))))) 54 | 55 | (defun pdf-test-relative-edges-p (edges) 56 | (and (consp edges) 57 | (cl-every (lambda (x) 58 | (and (numberp x) 59 | (<= x 1) 60 | (>= x 0))) 61 | edges))) 62 | 63 | (defmacro pdf-test-with-pdf (pdf-filename &rest body) 64 | (declare (indent 0) (debug t)) 65 | (let ((buffer (make-symbol "buffer"))) 66 | `(let ((,buffer (find-file-noselect 67 | (expand-file-name ,pdf-filename))) 68 | (pdf-info-epdfinfo-error-filename (make-temp-file "epdfinfo.log"))) 69 | (unwind-protect 70 | (progn 71 | (pdf-info-quit) 72 | (pdf-info-process-assert-running t) 73 | (with-current-buffer ,buffer ,@body)) 74 | (when (buffer-live-p ,buffer) 75 | (with-current-buffer ,buffer 76 | (set-buffer-modified-p nil) 77 | (let (kill-buffer-hook) 78 | (kill-buffer)))) 79 | (when (file-exists-p pdf-info-epdfinfo-error-filename) 80 | (with-temp-buffer 81 | (insert-file-contents pdf-info-epdfinfo-error-filename) 82 | (unless (= 1 (point-max)) 83 | (message ">>> %s <<<" (buffer-string)))) 84 | (delete-file pdf-info-epdfinfo-error-filename)) 85 | (pdf-info-quit))))) 86 | 87 | (defmacro pdf-test-with-test-pdf (&rest body) 88 | `(pdf-test-with-pdf "test.pdf" ,@body)) 89 | 90 | (defmacro pdf-test-with-encrypted-pdf (&rest body) 91 | `(pdf-test-with-pdf "encrypted.pdf" ,@body)) 92 | 93 | ;; --- 94 | (require 'f) 95 | (require 'undercover) 96 | (undercover "lisp/*.el") 97 | (require 'let-alist) 98 | (require 'pdf-info) 99 | (require 'ert) 100 | (require 'pdf-tools) 101 | 102 | ;;; test-helper.el ends here 103 | -------------------------------------------------------------------------------- /test/test.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/politza/pdf-tools/acbe49a44a8c3501171a3693fa1934eef40c869e/test/test.pdf -------------------------------------------------------------------------------- /test/test.tex: -------------------------------------------------------------------------------- 1 | \documentclass{article} 2 | 3 | \usepackage[ngerman]{babel} 4 | \usepackage[utf8]{inputenc} 5 | \usepackage[T1]{fontenc} 6 | \usepackage{lmodern} 7 | \usepackage{amsmath,amssymb,amsfonts,amstext,amsthm} 8 | \usepackage[rgb]{xcolor} 9 | \usepackage[author={PDF Tools}]{pdfcomment} 10 | \usepackage{attachfile} 11 | 12 | \pagestyle{empty} 13 | \begin{document} 14 | \section{Text} 15 | 16 | \label{sec:text} 17 | PDF Tools is, among other things, a replacement of DocView for PDF 18 | files. The key difference is, that pages are not prerendered by 19 | e.g. ghostscript and stored in the file-system, but rather created 20 | on-demand and stored in memory. 21 | 22 | PDF Tools is, among other things, a replacement of DocView for PDF 23 | files. The key difference is, that pages are not prerendered by 24 | e.g. ghostscript and stored in the file-system, but rather created 25 | on-demand and stored in memory. 26 | \newpage 27 | 28 | \section{Annotations} 29 | 30 | \label{sec:annotations} 31 | \begin{enumerate} 32 | \item \pdfcomment[color=red,icon=Insert]{insert} \\ 33 | \item \pdfmarkupcomment[markup=StrikeOut, color=red]{strikeout} \\ 34 | \item \pdfmarkupcomment[markup=Highlight, color=red]{highlight} \\ 35 | \item \pdfmarkupcomment[markup=Underline, color=red]{underline} \\ 36 | \item \pdfmarkupcomment[markup=Squiggly, color=red]{squiggly} \\ 37 | \end{enumerate} 38 | \newpage 39 | 40 | \section{Links} 41 | 42 | \label{sec:links} 43 | 44 | \begin{enumerate} 45 | \item Internal link: \ref{sec:text} 46 | \item External link: \url{http://www.gnu.org} 47 | \end{enumerate} 48 | \newpage 49 | 50 | \section{Attachments} 51 | 52 | \label{sec:attachments} 53 | \attachfile{test.tex} 54 | 55 | \newpage 56 | \section{Outline} 57 | 58 | \label{sec:outline} 59 | \subsection{subsection} 60 | \subsubsection{subsubsection} 61 | 62 | \newpage 63 | \indent 64 | % empty page 65 | \end{document} 66 | --------------------------------------------------------------------------------