From e47650852b8aa4da6d0b0cea3b5421955795cc64 Mon Sep 17 00:00:00 2001 From: Blendoit Date: Sat, 1 Aug 2020 15:24:03 -0700 Subject: Definitely /not/ including elpa/, that would be chaos. --- elpa/pdf-tools-20200512.1524/pdf-info.el | 1744 ------------------------------ 1 file changed, 1744 deletions(-) delete mode 100644 elpa/pdf-tools-20200512.1524/pdf-info.el (limited to 'elpa/pdf-tools-20200512.1524/pdf-info.el') diff --git a/elpa/pdf-tools-20200512.1524/pdf-info.el b/elpa/pdf-tools-20200512.1524/pdf-info.el deleted file mode 100644 index 0c345a2..0000000 --- a/elpa/pdf-tools-20200512.1524/pdf-info.el +++ /dev/null @@ -1,1744 +0,0 @@ -;;; pdf-info.el --- Extract info from pdf-files via a helper process. -*- lexical-binding: t -*- - -;; Copyright (C) 2013, 2014 Andreas Politz - -;; Author: Andreas Politz -;; Keywords: files, multimedia - -;; This program is free software; you can redistribute it and/or modify -;; it under the terms of the GNU General Public License as published by -;; the Free Software Foundation, either version 3 of the License, or -;; (at your option) any later version. - -;; This program is distributed in the hope that it will be useful, -;; but WITHOUT ANY WARRANTY; without even the implied warranty of -;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -;; GNU General Public License for more details. - -;; You should have received a copy of the GNU General Public License -;; along with this program. If not, see . - -;;; Commentary: -;; -;; This library represents the Lisp side of the epdfinfo server. This -;; program works on a command/response basis, but there should be no -;; need to understand the protocol, since every command has a -;; corresponding Lisp-function (see below under `High level -;; interface'). -;; -;; Most of these functions receive a file-or-buffer argument, which -;; may be what it says and defaults to the current buffer. Also, most -;; functions return some sort of alist, with, in most cases, -;; straight-forward key-value-pairs. Though some may be only -;; understandable in the context of Adobe's PDF spec \(Adobe -;; PDF32000\) or the poppler documentation (e.g. annotation flags). -;; -;; If the poppler library is fairly recent (>= 0.19.4, older versions -;; have a bug, which may corrupt the document), annotations maybe -;; modified to a certain degree, deleted and text-annotations created. -;; The state of these modifications is held in the server. In order -;; to realize, annotations retrieved or created are referenced by a -;; unique symbol. Saving these changes creates a new file, the -;; original document is never touched. - -;;; Todo: -;; -;; + Close documents at some time (e.g. when the buffer is killed) -;; - -;;; Code: - -(require 'tq) -(require 'cl-lib) - - - -;; * ================================================================== * -;; * Customizations -;; * ================================================================== * - -(defgroup pdf-info nil - "Extract infos from pdf-files via a helper process." - :group 'pdf-tools) - -(defcustom pdf-info-epdfinfo-program - (let ((executable (if (eq system-type 'windows-nt) - "epdfinfo.exe" "epdfinfo")) - (default-directory - (or (and load-file-name - (file-name-directory load-file-name)) - default-directory))) - (cl-labels ((try-directory (directory) - (and (file-directory-p directory) - (file-executable-p (expand-file-name executable directory)) - (expand-file-name executable directory)))) - (or (executable-find executable) - ;; This works if epdfinfo is in the same place as emacs and - ;; the editor was started with an absolute path, i.e. it is - ;; meant for Windows/Msys2. - (and (stringp (car-safe command-line-args)) - (file-name-directory (car command-line-args)) - (try-directory - (file-name-directory (car command-line-args)))) - ;; If we are running directly from the git repo. - (try-directory (expand-file-name "../server")) - ;; Fall back to epdfinfo in the directory of this file. - (expand-file-name executable)))) - "Filename of the epdfinfo executable." - :group 'pdf-info - :type 'file) - -(defcustom pdf-info-epdfinfo-error-filename nil - "Filename for error output of the epdfinfo executable. - -If nil, discard any error messages. Useful for debugging." - :group 'pdf-info - :type `(choice (const :tag "None" nil) - ,@(when (file-directory-p "/tmp/") - '((const "/tmp/epdfinfo.log"))) - (file))) - -(defcustom pdf-info-log nil - "Whether to log the communication with the server. - -If this is non-nil, all communication with the epdfinfo program -will be logged to the buffer \"*pdf-info-log*\"." - :group 'pdf-info - :type 'boolean) - -(defcustom pdf-info-log-entry-max 512 - "Maximum number of characters in a single log entry. - -This variable has no effect if `pdf-info-log' is nil." - :group 'pdf-info - :type 'integer) - -(defcustom pdf-info-restart-process-p 'ask - "What to do when the epdfinfo server died. - -This should be one of -nil -- do nothing, -t -- automatically restart it or -ask -- ask whether to restart or not. - -If it is `ask', the server quits and you answer no, this variable -is set to nil." - :group 'pdf-info - :type '(choice (const :tag "Do nothing" nil) - (const :tag "Restart silently" t) - (const :tag "Always ask" ask))) - -(defcustom pdf-info-close-document-hook nil - "A hook ran after a document was closed in the server. - -The hook is run in the documents buffer, if it exists. Otherwise -in a `with-temp-buffer' form." - :group 'pdf-info - :type 'hook) - - - -;; * ================================================================== * -;; * Variables -;; * ================================================================== * - -(defvar pdf-info-asynchronous nil - "If non-nil process queries asynchronously. - -More specifically the value should be a function of at 2 -arguments \(fn STATUS RESPONSE\), where STATUS is either nil, for -a successful query, or the symbol error. RESPONSE is either the -command's response or the error message. This does not work -recursive, i.e. if function wants to make another asynchronous -query it has to rebind this variable. - -Alternatively it may be a list \(FN . ARGS\), in which case FN -will be invoked like \(apply FN STATUS RESPONSE ARGS\). - -Also, all pdf-info functions normally returning a response return -nil. - -This variable should only be let-bound.") - -(defconst pdf-info-pdf-date-regexp - ;; Adobe PDF32000.book, 7.9.4 Dates - (eval-when-compile - (concat - ;; allow for preceding garbage - ;;"\\`" - "[dD]:" - "\\([0-9]\\{4\\}\\)" ;year - "\\(?:" - "\\([0-9]\\{2\\}\\)" ;month - "\\(?:" - "\\([0-9]\\{2\\}\\)" ;day - "\\(?:" - "\\([0-9]\\{2\\}\\)" ;hour - "\\(?:" - "\\([0-9]\\{2\\}\\)" ;minutes - "\\(?:" - "\\([0-9]\\{2\\}\\)" ;seconds - "\\)?\\)?\\)?\\)?\\)?" - "\\(?:" - "\\([+-Zz]\\)" ;UT delta char - "\\(?:" - "\\([0-9]\\{2\\}\\)" ;UT delta hours - "\\(?:" - "'" - "\\([0-9]\\{2\\}\\)" ;UT delta minutes - "\\)?\\)?\\)?" - ;; "\\'" - ;; allow for trailing garbage - ))) - -(defvar pdf-info--queue t - "Internally used transmission-queue for the server. - -This variable is initially `t', telling the code starting the -server, that it never ran.") - - -;; * ================================================================== * -;; * Process handling -;; * ================================================================== * - -(defconst pdf-info-empty-page-data - (eval-when-compile - (concat - "%PDF-1.0\n1 0 obj<>endobj 2 0" - " obj<>endobj 3 0 obj<>endobj\nxref\n0 4\n00000000" - "0065535 f\n0000000010 00000 n\n0000000053 00000 n\n00000" - "00102 00000 n\ntrailer<>\nstartxref\n149\n%EOF")) - "PDF data of an empty page.") - -(defun pdf-info-process () - "Return the process object or nil." - (and pdf-info--queue - (not (eq t pdf-info--queue)) - (tq-process pdf-info--queue))) - -(defun pdf-info-check-epdfinfo (&optional interactive-p) - "Check if the server should be working properly. - -Signal an error if some problem was found. Message a -confirmation, if INTERACTIVE-P is non-nil and no problems were -found. - -Returns nil." - (interactive "p") - (let ((executable pdf-info-epdfinfo-program)) - (unless (stringp executable) - (error "pdf-info-epdfinfo-program is unset or not a string")) - (unless (file-executable-p executable) - (error "pdf-info-epdfinfo-program is not executable")) - (when pdf-info-epdfinfo-error-filename - (unless (and (stringp pdf-info-epdfinfo-error-filename) - (file-writable-p pdf-info-epdfinfo-error-filename)) - (error "pdf-info-epdfinfo-error-filename should contain writable filename"))) - (let* ((default-directory (expand-file-name "~/")) - (cmdfile (make-temp-file "commands")) - (pdffile (make-temp-file "empty.pdf")) - (tempdir (make-temp-file "tmpdir" t)) - (process-environment (cons (concat "TMPDIR=" tempdir) - process-environment))) - (unwind-protect - (with-temp-buffer - (with-temp-file pdffile - (set-buffer-multibyte nil) - (insert pdf-info-empty-page-data)) - (with-temp-file cmdfile - (insert (format "renderpage:%s:1:100\nquit\n" - (pdf-info-query--escape pdffile)))) - (unless (= 0 (apply #'call-process - executable cmdfile (current-buffer) - nil (when pdf-info-epdfinfo-error-filename - (list pdf-info-epdfinfo-error-filename)))) - (error "Error running `%s': %s" - pdf-info-epdfinfo-program - (buffer-string)))) - (when (file-exists-p cmdfile) - (delete-file cmdfile)) - (when (file-exists-p pdffile) - (delete-file pdffile)) - (when (file-exists-p tempdir) - (delete-directory tempdir t))))) - (when interactive-p - (message "The epdfinfo program appears to be working.")) - nil) - -(defun pdf-info-process-assert-running (&optional force) - "Assert a running process. - -If it never ran, i.e. `pdf-info-process' is t, start it -unconditionally. - -Otherwise, if FORCE is non-nil start it, if it is not running. -Else restart it with respect to the variable -`pdf-info-restart-process-p', which see. - -If getting the process to run fails, this function throws an -error." - (interactive "P") - (unless (and (processp (pdf-info-process)) - (eq (process-status (pdf-info-process)) - 'run)) - (when (pdf-info-process) - (tq-close pdf-info--queue) - (setq pdf-info--queue nil)) - (unless (or force - (eq pdf-info--queue t) - (and (eq pdf-info-restart-process-p 'ask) - (not noninteractive) - (y-or-n-p "The epdfinfo server quit, restart it ? ")) - (and pdf-info-restart-process-p - (not (eq pdf-info-restart-process-p 'ask)))) - - (when (eq pdf-info-restart-process-p 'ask) - (setq pdf-info-restart-process-p nil)) - (error "The epdfinfo server quit")) - (pdf-info-check-epdfinfo) - (let* ((process-connection-type) ;Avoid 4096 Byte bug #12440. - (default-directory "~") - (proc (apply #'start-process - "epdfinfo" " *epdfinfo*" pdf-info-epdfinfo-program - (when pdf-info-epdfinfo-error-filename - (list pdf-info-epdfinfo-error-filename))))) - (with-current-buffer " *epdfinfo*" - (erase-buffer)) - (set-process-query-on-exit-flag proc nil) - (set-process-coding-system proc 'utf-8-unix 'utf-8-unix) - (setq pdf-info--queue (tq-create proc)))) - pdf-info--queue) - -(defadvice tq-process-buffer (around bugfix activate) - "Fix a bug in trunk where the wrong callback gets called." - ;; FIXME: Make me iterative. - (let ((tq (ad-get-arg 0))) - (if (not (equal (car (process-command (tq-process tq))) - pdf-info-epdfinfo-program)) - ad-do-it - (let ((buffer (tq-buffer tq)) - done) - (when (buffer-live-p buffer) - (set-buffer buffer) - (while (and (not done) - (> (buffer-size) 0)) - (setq done t) - (if (tq-queue-empty tq) - (let ((buf (generate-new-buffer "*spurious*"))) - (copy-to-buffer buf (point-min) (point-max)) - (delete-region (point-min) (point)) - (pop-to-buffer buf nil) - (error "Spurious communication from process %s, see buffer %s" - (process-name (tq-process tq)) - (buffer-name buf))) - (goto-char (point-min)) - (when (re-search-forward (tq-queue-head-regexp tq) nil t) - (setq done nil) - (let ((answer (buffer-substring (point-min) (point))) - (fn (tq-queue-head-fn tq)) - (closure (tq-queue-head-closure tq))) - (delete-region (point-min) (point)) - (tq-queue-pop tq) - (condition-case-unless-debug err - (funcall fn closure answer) - (error - (message "Error while processing tq callback: %s" - (error-message-string err))))))))))))) - - -;; * ================================================================== * -;; * Sending and receiving -;; * ================================================================== * - -(defun pdf-info-query (cmd &rest args) - "Query the server using CMD and ARGS." - (pdf-info-process-assert-running) - (unless (symbolp cmd) - (setq cmd (intern cmd))) - (let* ((query (concat (mapconcat 'pdf-info-query--escape - (cons cmd args) ":") "\n")) - (callback - (lambda (closure response) - (cl-destructuring-bind (status &rest result) - (pdf-info-query--parse-response cmd response) - (pdf-info-query--log response) - (let* (pdf-info-asynchronous) - (if (functionp closure) - (funcall closure status result) - (apply (car closure) status result (cdr closure))))))) - response status done - (closure (or pdf-info-asynchronous - (lambda (s r) - (setq status s response r done t))))) - (pdf-info-query--log query t) - (tq-enqueue - pdf-info--queue query "^\\.\n" closure callback) - (unless pdf-info-asynchronous - (while (and (not done) - (eq (process-status (pdf-info-process)) - 'run)) - (accept-process-output (pdf-info-process) 0.01)) - (when (and (not done) - (not (eq (process-status (pdf-info-process)) - 'run)) - (not (eq cmd 'quit))) - (error "The epdfinfo server quit unexpectedly.")) - (cond - ((null status) response) - ((eq status 'error) - (error "epdfinfo: %s" response)) - ((eq status 'interrupted) - (error "epdfinfo: Command was interrupted")) - (t - (error "internal error: invalid response status")))))) - -(defun pdf-info-interrupt () - "FIXME: This command does currently nothing." - (when (and (processp (pdf-info-process)) - (eq (process-status (pdf-info-process)) - 'run)) - (signal-process (pdf-info-process) 'SIGUSR1))) - -(defun pdf-info-query--escape (arg) - "Escape ARG for transmission to the server." - (if (null arg) - (string) - (with-current-buffer (get-buffer-create " *pdf-info-query--escape*") - (erase-buffer) - (insert (format "%s" arg)) - (goto-char 1) - (while (not (eobp)) - (cond - ((memq (char-after) '(?\\ ?:)) - (insert ?\\)) - ((eq (char-after) ?\n) - (delete-char 1) - (insert ?\\ ?n) - (backward-char))) - (forward-char)) - (buffer-substring-no-properties 1 (point-max))))) - -(defmacro pdf-info-query--read-record () - "Read a single record of the response in current buffer." - `(let (records done (beg (point))) - (while (not done) - (cl-case (char-after) - (?\\ - (delete-char 1) - (if (not (eq (char-after) ?n)) - (forward-char) - (delete-char 1) - (insert ?\n))) - ((?: ?\n) - (push (buffer-substring-no-properties - beg (point)) records) - (forward-char) - (setq beg (point) - done (bolp))) - (t (forward-char)))) - (nreverse records))) - -(defun pdf-info-query--parse-response (cmd response) - "Parse one epdfinfo RESPONSE to CMD. - -Returns a cons \(STATUS . RESULT\), where STATUS is one of nil -for a regular response, error for an error \(RESULT contains the -error message\) or interrupted, i.e. the command was -interrupted." - (with-current-buffer - (get-buffer-create " *pdf-info-query--parse-response*") - (erase-buffer) - (insert response) - (goto-char 1) - (cond - ((looking-at "ERR\n") - (forward-line) - (cons 'error (buffer-substring-no-properties - (point) - (progn - (re-search-forward "^\\.\n") - (1- (match-beginning 0)))))) - ((looking-at "OK\n") - (let (result) - (forward-line) - (while (not (and (= (char-after) ?.) - (= (char-after (1+ (point))) ?\n))) - (push (pdf-info-query--read-record) result)) - (cons nil (pdf-info-query--transform-response - cmd (nreverse result))))) - ((looking-at "INT\n") - (cons 'interrupted nil)) - (t - (cons 'error "Invalid server response"))))) - -(defun pdf-info-query--transform-response (cmd response) - "Transform a RESPONSE to CMD into a Lisp form." - (cl-case cmd - (open nil) - (close (equal "1" (caar response))) - (number-of-pages (string-to-number (caar response))) - (charlayout - (mapcar (lambda (elt) - (cl-assert (= 1 (length (cadr elt))) t) - `(,(aref (cadr elt) 0) - ,(mapcar 'string-to-number - (split-string (car elt) " " t)))) - response)) - (regexp-flags - (mapcar (lambda (elt) - (cons (intern (car elt)) - (string-to-number (cadr elt)))) - response)) - ((search-string search-regexp) - (mapcar - (lambda (r) - `((page . ,(string-to-number (nth 0 r))) - (text . ,(let (case-fold-search) - (pdf-util-highlight-regexp-in-string - (regexp-quote (nth 1 r)) (nth 2 r)))) - (edges . ,(mapcar (lambda (m) - (mapcar 'string-to-number - (split-string m " " t))) - (cddr (cdr r)))))) - response)) - (outline - (mapcar (lambda (r) - `((depth . ,(string-to-number (pop r))) - ,@(pdf-info-query--transform-action r))) - response)) - (pagelinks - (mapcar (lambda (r) - `((edges . - ,(mapcar 'string-to-number ;area - (split-string (pop r) " " t))) - ,@(pdf-info-query--transform-action r))) - response)) - (metadata - (let ((md (car response))) - (if (= 1 (length md)) - (list (cons 'title (car md))) - (list - (cons 'title (pop md)) - (cons 'author (pop md)) - (cons 'subject (pop md)) - (cons 'keywords-raw (car md)) - (cons 'keywords (split-string (pop md) "[\t\n ]*,[\t\n ]*" t)) - (cons 'creator (pop md)) - (cons 'producer (pop md)) - (cons 'format (pop md)) - (cons 'created (pop md)) - (cons 'modified (pop md)))))) - (gettext - (or (caar response) "")) - (getselection - (mapcar (lambda (line) - (mapcar 'string-to-number - (split-string (car line) " " t))) - response)) - (features (mapcar 'intern (car response))) - (pagesize - (setq response (car response)) - (cons (round (string-to-number (car response))) - (round (string-to-number (cadr response))))) - ((getannot editannot addannot) - (pdf-info-query--transform-annotation (car response))) - (getannots - (mapcar 'pdf-info-query--transform-annotation response)) - (getattachments - (mapcar 'pdf-info-query--transform-attachment response)) - ((getattachment-from-annot) - (pdf-info-query--transform-attachment (car response))) - (boundingbox - (mapcar 'string-to-number (car response))) - (synctex-forward-search - (let ((list (mapcar 'string-to-number (car response)))) - `((page . ,(car list)) - (edges . ,(cdr list))))) - (synctex-backward-search - `((filename . ,(caar response)) - (line . ,(string-to-number (cadr (car response)))) - (column . ,(string-to-number (cadr (cdar response)))))) - (delannot nil) - ((save) (caar response)) - ((renderpage renderpage-text-regions renderpage-highlight) - (pdf-util-munch-file (caar response))) - ((setoptions getoptions) - (let (options) - (dolist (key-value response) - (let ((key (intern (car key-value))) - (value (cadr key-value))) - (cl-case key - ((:render/printed :render/usecolors) - (setq value (equal value "1")))) - (push value options) - (push key options))) - options)) - (pagelabels (mapcar 'car response)) - (ping (caar response)) - (t response))) - - -(defun pdf-info-query--transform-action (action) - "Transform ACTION response into a Lisp form." - (let ((type (intern (pop action)))) - `((type . ,type) - (title . ,(pop action)) - ,@(cl-case type - (goto-dest - `((page . ,(string-to-number (pop action))) - (top . ,(and (> (length (car action)) 0) - (string-to-number (pop action)))))) - (goto-remote - `((filename . ,(pop action)) - (page . ,(string-to-number (pop action))) - (top . ,(and (> (length (car action)) 0) - (string-to-number (pop action)))))) - (t `((uri . ,(pop action)))))))) - -(defun pdf-info-query--transform-annotation (a) - (cl-labels ((not-empty (s) - (if (not (equal s "")) s))) - (let (a1 a2 a3) - (cl-destructuring-bind (page edges type id flags color contents modified &rest rest) - a - (setq a1 `((page . ,(string-to-number page)) - (edges . ,(mapcar 'string-to-number - (split-string edges " " t))) - (type . ,(intern type)) - (id . ,(intern id)) - (flags . ,(string-to-number flags)) - (color . ,(not-empty color)) - (contents . ,contents) - (modified . ,(pdf-info-parse-pdf-date modified)))) - (when rest - (cl-destructuring-bind (label subject opacity popup-edges popup-is-open created - &rest rest) - rest - (setq a2 - `((label . ,(not-empty label)) - (subject . ,(not-empty subject)) - (opacity . ,(let ((o (not-empty opacity))) - (and o (string-to-number o)))) - (popup-edges . ,(let ((p (not-empty popup-edges))) - (when p - (mapcar 'string-to-number - (split-string p " " t))))) - (popup-is-open . ,(equal popup-is-open "1")) - (created . ,(pdf-info-parse-pdf-date (not-empty created))))) - (cond - ((eq (cdr (assoc 'type a1)) 'text) - (cl-destructuring-bind (icon state is-open) - rest - (setq a3 - `((icon . ,(not-empty icon)) - (state . ,(not-empty state)) - (is-open . ,(equal is-open "1")))))) - ((memq (cdr (assoc 'type a1)) - '(squiggly highlight underline strike-out)) - (setq a3 `((markup-edges - . ,(mapcar (lambda (r) - (mapcar 'string-to-number - (split-string r " " t))) - rest))))))))) - (append a1 a2 a3)))) - -(defun pdf-info-query--transform-attachment (a) - (cl-labels ((not-empty (s) - (if (not (equal s "")) s))) - (cl-destructuring-bind (id filename description size modified - created checksum file) - a - `((id . ,(intern id)) - (filename . ,(not-empty filename)) - (description . ,(not-empty description)) - (size . ,(let ((n (string-to-number size))) - (and (>= n 0) n))) - (modified . ,(not-empty modified)) - (created . ,(not-empty created)) - (checksum . ,(not-empty checksum)) - (file . ,(not-empty file)))))) - -(defun pdf-info-query--log (string &optional query-p) - "Log STRING as query/response, depending on QUERY-P. - -This is a no-op, if `pdf-info-log' is nil." - (when pdf-info-log - (with-current-buffer (get-buffer-create "*pdf-info-log*") - (buffer-disable-undo) - (let ((pos (point-max)) - (window (get-buffer-window))) - (save-excursion - (goto-char (point-max)) - (unless (bolp) - (insert ?\n)) - (insert - (propertize - (format-time-string "%H:%M:%S ") - 'face - (if query-p - 'font-lock-keyword-face - 'font-lock-function-name-face)) - (if (and (numberp pdf-info-log-entry-max) - (> (length string) - pdf-info-log-entry-max)) - (concat (substring string 0 pdf-info-log-entry-max) - "...[truncated]\n") - string))) - (when (and (window-live-p window) - (= pos (window-point window))) - (set-window-point window (point-max))))))) - - - -;; * ================================================================== * -;; * Utility functions -;; * ================================================================== * - -(defvar doc-view-buffer-file-name) -(defvar doc-view--buffer-file-name) - -(defun pdf-info--normalize-file-or-buffer (file-or-buffer) - "Return the PDF file corresponding to FILE-OR-BUFFER. - -FILE-OR-BUFFER may be nil, a PDF buffer, the name of a PDF buffer -or a PDF file." - (unless file-or-buffer - (setq file-or-buffer - (current-buffer))) - (when (bufferp file-or-buffer) - (unless (buffer-live-p file-or-buffer) - (error "Buffer is not live :%s" file-or-buffer)) - (with-current-buffer file-or-buffer - (unless (setq file-or-buffer - (cl-case major-mode - (doc-view-mode - (cond ((boundp 'doc-view-buffer-file-name) - doc-view-buffer-file-name) - ((boundp 'doc-view--buffer-file-name) - doc-view--buffer-file-name))) - (pdf-view-mode (pdf-view-buffer-file-name)) - (t (buffer-file-name)))) - (error "Buffer is not associated with any file :%s" (buffer-name))))) - (unless (stringp file-or-buffer) - (signal 'wrong-type-argument - (list 'stringp 'bufferp 'null file-or-buffer))) - ;; is file - (when (file-remote-p file-or-buffer) - (error "Processing remote files not supported :%s" - file-or-buffer)) - ;; (unless (file-readable-p file-or-buffer) - ;; (error "File not readable :%s" file-or-buffer)) - (expand-file-name file-or-buffer)) - -(defun pdf-info-valid-page-spec-p (pages) - "The type predicate for a valid page-spec." - (not (not (ignore-errors (pdf-info-normalize-page-range pages))))) - -(defun pdf-info-normalize-page-range (pages) - "Normalize PAGES for sending to the server. - -PAGES may be a single page number, a cons \(FIRST . LAST\), or -nil, which stands for all pages. - -The result is a cons \(FIRST . LAST\), where LAST may be 0 -representing the final page." - (cond - ((natnump pages) - (cons pages pages)) - ((null pages) - (cons 1 0)) - ((and (natnump (car pages)) - (natnump (cdr pages))) - pages) - (t - (signal 'wrong-type-argument - (list 'pdf-info-valid-page-spec-p pages))))) - -(defun pdf-info-parse-pdf-date (date) - (when (and date - (string-match pdf-info-pdf-date-regexp date)) - (let ((year (match-string 1 date)) - (month (match-string 2 date)) - (day (match-string 3 date)) - (hour (match-string 4 date)) - (min (match-string 5 date)) - (sec (match-string 6 date)) - (ut-char (match-string 7 date)) - (ut-hour (match-string 8 date)) - (ut-min (match-string 9 date)) - (tz 0)) - (when (or (equal ut-char "+") - (equal ut-char "-")) - (when ut-hour - (setq tz (* 3600 (string-to-number ut-hour)))) - (when ut-min - (setq tz (+ tz (* 60 (string-to-number ut-min))))) - (when (equal ut-char "-") - (setq tz (- tz)))) - (encode-time - (if sec (string-to-number sec) 0) - (if min (string-to-number min) 0) - (if hour (string-to-number hour) 0) - (if day (string-to-number day) 1) - (if month (string-to-number month) 1) - (string-to-number year) - tz)))) - -(defmacro pdf-info-compose-queries (let-forms &rest body) - "Let-bind each VAR to QUERIES results and evaluate BODY. - -All queries in each QUERIES form are run by the server in the -order they appear and the results collected in a list, which is -bound to VAR. Then BODY is evaluated and its value becomes the -final result of all queries, unless at least one of them provoked -an error. In this case BODY is ignored and the error is the -result. - -This macro handles synchronous and asynchronous calls, -i.e. `pdf-info-asynchronous' is non-nil, transparently. - -\(FN \(\(VAR QUERIES\)...\) BODY\)" - (declare (indent 1) - (debug ((&rest &or - (symbolp &optional form) - symbolp) - body))) - (unless (cl-every (lambda (form) - (when (symbolp form) - (setq form (list form))) - (and (consp form) - (symbolp (car form)) - (listp (cdr form)))) - let-forms) - (error "Invalid let-form: %s" let-forms)) - - (setq let-forms (mapcar (lambda (form) - (if (symbolp form) - (list form) - form)) - let-forms)) - (let* ((status (make-symbol "status")) - (response (make-symbol "response")) - (first-error (make-symbol "first-error")) - (done (make-symbol "done")) - (callback (make-symbol "callback")) - (results (make-symbol "results")) - (push-fn (make-symbol "push-fn")) - (terminal-fn (make-symbol "terminal-fn")) - (buffer (make-symbol "buffer"))) - `(let* (,status - ,response ,first-error ,done - (,buffer (current-buffer)) - (,callback pdf-info-asynchronous) - ;; Ensure a new alist on every invocation. - (,results (mapcar 'copy-sequence - ',(cl-mapcar (lambda (form) - (list (car form))) - let-forms))) - (,push-fn (lambda (status result var) - ;; Store result in alist RESULTS under key - ;; VAR. - (if status - (unless ,first-error - (setq ,first-error result)) - (let ((elt (assq var ,results))) - (setcdr elt (append (cdr elt) - (list result))))))) - (,terminal-fn - (lambda (&rest _) - ;; Let-bind responses corresponding to their variables, - ;; i.e. keys in alist RESULTS. - (let (,@(mapcar (lambda (var) - (list var (list 'cdr (list 'assq (list 'quote var) - results)))) - (mapcar 'car let-forms))) - (setq ,status (not (not ,first-error)) - ,response (or ,first-error - (with-current-buffer ,buffer - ,@body)) - ,done t) - ;; Maybe invoke the CALLBACK (which was bound to - ;; pdf-info-asynchronous). - (when ,callback - (if (functionp ,callback) - (funcall ,callback ,status ,response) - (apply (car ,callback) - ,status ,response (cdr ,callback)))))))) - ;; Wrap each query in an asynchronous call, with its VAR as - ;; callback argument, so the PUSH-FN can put it in the alist - ;; RESULTS. - ,@(mapcar (lambda (form) - (list 'let (list - (list 'pdf-info-asynchronous - (list 'list push-fn (list 'quote (car form))))) - (cadr form))) - let-forms) - ;; Request a no-op, just so we know that we are finished. - (let ((pdf-info-asynchronous ,terminal-fn)) - (pdf-info-ping)) - ;; CALLBACK is the original value of pdf-info-asynchronous. If - ;; nil, this is a synchronous query. - (unless ,callback - (while (and (not ,done) - (eq (process-status (pdf-info-process)) - 'run)) - (accept-process-output (pdf-info-process) 0.01)) - (when (and (not ,done) - (not (eq (process-status (pdf-info-process)) - 'run))) - (error "The epdfinfo server quit unexpectedly.")) - (when ,status - (error "epdfinfo: %s" ,response)) - ,response)))) - - -;; * ================================================================== * -;; * Buffer local server instances -;; * ================================================================== * - -(put 'pdf-info--queue 'permanent-local t) - -(defun pdf-info-make-local-server (&optional buffer force-restart-p) - "Create a server instance local to BUFFER. - -Does nothing if BUFFER already has a local instance. Unless -FORCE-RESTART-P is non-nil, then quit a potential process and -restart it." - (unless buffer - (setq buffer (current-buffer))) - (with-current-buffer buffer - (unless (and - (not force-restart-p) - (local-variable-p 'pdf-info--queue) - (processp (pdf-info-process)) - (eq (process-status (pdf-info-process)) - 'run)) - (when (and (local-variable-p 'pdf-info--queue) - (processp (pdf-info-process))) - (tq-close pdf-info--queue)) - (set (make-local-variable 'pdf-info--queue) nil) - (pdf-info-process-assert-running t) - (add-hook 'kill-buffer-hook 'pdf-info-kill-local-server nil t) - pdf-info--queue))) - -(defun pdf-info-kill-local-server (&optional buffer) - "Kill the local server in BUFFER. - -A No-op, if BUFFER has not running server instance." - (save-current-buffer - (when buffer - (set-buffer buffer)) - (when (local-variable-p 'pdf-info--queue) - (pdf-info-kill) - (kill-local-variable 'pdf-info--queue) - t))) - -(defun pdf-info-local-server-p (&optional buffer) - "Return non-nil, if BUFFER has a running server instance." - (unless buffer - (setq buffer (current-buffer))) - (setq buffer (get-buffer buffer)) - (and (buffer-live-p buffer) - (local-variable-p 'pdf-info--queue buffer))) - -(defun pdf-info-local-batch-query (producer-fn - consumer-fn - sentinel-fn - args) - "Process a set of queries asynchronously in a local instance." - (unless (pdf-info-local-server-p) - (error "Create a local server first")) - (let* ((buffer (current-buffer)) - (producer-symbol (make-symbol "producer")) - (consumer-symbol (make-symbol "consumer")) - (producer - (lambda (args) - (if (null args) - (funcall sentinel-fn 'finished buffer) - (let ((pdf-info-asynchronous - (apply-partially - (symbol-function consumer-symbol) - args))) - (cond - ((pdf-info-local-server-p buffer) - (with-current-buffer buffer - (apply producer-fn (car args)))) - (t - (funcall sentinel-fn 'error buffer))))))) - (consumer (lambda (args status result) - (if (not (pdf-info-local-server-p buffer)) - (funcall sentinel-fn 'error buffer) - (with-current-buffer buffer - (apply consumer-fn status result (car args))) - (funcall (symbol-function producer-symbol) - (cdr args)))))) - (fset producer-symbol producer) - (fset consumer-symbol consumer) - (funcall producer args))) - - - -;; * ================================================================== * -;; * High level interface -;; * ================================================================== * - -(defvar pdf-info-features nil) - -(defun pdf-info-features () - "Return a list of symbols describing compile-time features." - (or pdf-info-features - (setq pdf-info-features - (let (pdf-info-asynchronous) - (pdf-info-query 'features))))) - -(defun pdf-info-writable-annotations-p () - (not (null (memq 'writable-annotations (pdf-info-features))))) - -(defun pdf-info-markup-annotations-p () - (not (null (memq 'markup-annotations (pdf-info-features))))) - -(defmacro pdf-info-assert-writable-annotations () - `(unless (memq 'writable-annotations (pdf-info-features)) - (error "Writing annotations is not supported by this version of epdfinfo"))) - -(defmacro pdf-info-assert-markup-annotations () - `(unless (memq 'markup-annotations (pdf-info-features)) - (error "Creating markup annotations is not supported by this version of epdfinfo"))) - -(defun pdf-info-creatable-annotation-types () - (let ((features (pdf-info-features))) - (cond - ((not (memq 'writable-annotations features)) nil) - ((memq 'markup-annotations features) - (list 'text 'squiggly 'underline 'strike-out 'highlight)) - (t (list 'text))))) - -(defun pdf-info-open (&optional file-or-buffer password) - "Open the document FILE-OR-BUFFER using PASSWORD. - -Generally, documents are opened and closed automatically on -demand, so this function is rarely needed, unless a PASSWORD is -set on the document. - -Manually opened documents are never closed automatically." - - (pdf-info-query - 'open (pdf-info--normalize-file-or-buffer file-or-buffer) - password)) - -(defun pdf-info-close (&optional file-or-buffer) - "Close the document FILE-OR-BUFFER. - -Returns t, if the document was actually open, otherwise nil. -This command is rarely needed, see also `pdf-info-open'." - (let* ((pdf (pdf-info--normalize-file-or-buffer file-or-buffer)) - (buffer (find-buffer-visiting pdf))) - (prog1 - (pdf-info-query 'close pdf) - (if (buffer-live-p buffer) - (with-current-buffer buffer - (run-hooks 'pdf-info-close-document-hook)) - (with-temp-buffer - (run-hooks 'pdf-info-close-document-hook)))))) - -(defun pdf-info-encrypted-p (&optional file-or-buffer) - "Return non-nil if FILE-OR-BUFFER requires a password. - -Note: This function returns nil, if the document is encrypted, -but was already opened (presumably using a password)." - - (condition-case err - (pdf-info-open - (pdf-info--normalize-file-or-buffer file-or-buffer)) - (error (or (string-match-p - ":Document is encrypted\\'" (cadr err)) - (signal (car err) (cdr err)))))) - -(defun pdf-info-metadata (&optional file-or-buffer) - "Extract the metadata from the document FILE-OR-BUFFER. - -This returns an alist containing some information about the -document." - (pdf-info-query - 'metadata - (pdf-info--normalize-file-or-buffer file-or-buffer))) - -(defun pdf-info-search-string (string &optional pages file-or-buffer) - "Search for STRING in PAGES of document FILE-OR-BUFFER. - -See `pdf-info-normalize-page-range' for valid PAGES formats. - -This function returns a list of matches. Each item is an alist -containing keys PAGE, TEXT and EDGES, where PAGE and TEXT are the -matched page resp. line. EDGES is a list containing a single -edges element \(LEFT TOP RIGHT BOTTOM\). This is for consistency -with `pdf-info-search-regexp', which may return matches with -multiple edges. - -The TEXT contains `match' face properties on the matched parts. - -Search is case-insensitive, unless `case-fold-search' is nil and -searching case-sensitive is supported by the server." - - (let ((pages (pdf-info-normalize-page-range pages))) - (pdf-info-query - 'search-string - (pdf-info--normalize-file-or-buffer file-or-buffer) - (car pages) - (cdr pages) - string - (if case-fold-search 1 0)))) - -(defvar pdf-info-regexp-compile-flags nil - "PCRE compile flags. - -Don't use this, but the equally named function.") - -(defvar pdf-info-regexp-match-flags nil - "PCRE match flags. - -Don't use this, but the equally named function.") - -(defun pdf-info-regexp-compile-flags () - (or pdf-info-regexp-compile-flags - (let* (pdf-info-asynchronous - (flags (pdf-info-query 'regexp-flags)) - (match (cl-remove-if-not - (lambda (flag) - (string-match-p - "\\`match-" (symbol-name (car flag)))) - flags)) - (compile (cl-set-difference flags match))) - (setq pdf-info-regexp-compile-flags compile - pdf-info-regexp-match-flags match) - pdf-info-regexp-compile-flags))) - -(defun pdf-info-regexp-match-flags () - (or pdf-info-regexp-match-flags - (progn - (pdf-info-regexp-compile-flags) - pdf-info-regexp-match-flags))) - -(defvar pdf-info-regexp-flags '(multiline) - "Compile- and match-flags for the PCRE engine. - -This is a list of symbols denoting compile- and match-flags when -searching for regular expressions. - -You should not change this directly, but rather `let'-bind it -around a call to `pdf-info-search-regexp'. - -Valid compile-flags are: - -newline-crlf, newline-lf, newline-cr, dupnames, optimize, -no-auto-capture, raw, ungreedy, dollar-endonly, anchored, -extended, dotall, multiline and caseless. - -Note that the last one, caseless, is handled special, as it is -always added if `case-fold-search' is non-nil. - -And valid match-flags: - -match-anchored, match-notbol, match-noteol, match-notempty, -match-partial, match-newline-cr, match-newline-lf, -match-newline-crlf and match-newline-any. - -See the glib documentation at url -`https://developer.gnome.org/glib/stable/glib-Perl-compatible-regular-expressions.html'.") - -(defun pdf-info-search-regexp (pcre &optional pages - no-error - file-or-buffer) - "Search for a PCRE on PAGES of document FILE-OR-BUFFER. - -See `pdf-info-normalize-page-range' for valid PAGES formats and -`pdf-info-search-string' for its return value. - -Uses the flags in `pdf-info-regexp-flags', which see. If -`case-fold-search' is non-nil, the caseless flag is added. - -If NO-ERROR is non-nil, catch errors due to invalid regexps and -return nil. If it is the symbol `invalid-regexp', then re-signal -this kind of error as a `invalid-regexp' error." - - (cl-labels ((orflags (flags alist) - (cl-reduce - (lambda (v flag) - (let ((n - (cdr (assq flag alist)))) - (if n (logior n v) v))) - (cons 0 flags)))) - (let ((pages (pdf-info-normalize-page-range pages))) - (condition-case err - (pdf-info-query - 'search-regexp - (pdf-info--normalize-file-or-buffer file-or-buffer) - (car pages) - (cdr pages) - pcre - (orflags `(,(if case-fold-search - 'caseless) - ,@pdf-info-regexp-flags) - (pdf-info-regexp-compile-flags)) - (orflags pdf-info-regexp-flags - (pdf-info-regexp-match-flags))) - (error - (let ((re - (concat "\\`epdfinfo: *Invalid *regexp: *" - ;; glib error - "\\(?:Error while compiling regular expression" - " *%s *\\)?\\(.*\\)"))) - (if (or (null no-error) - (not (string-match - (format re (regexp-quote pcre)) - (cadr err)))) - (signal (car err) (cdr err)) - (if (eq no-error 'invalid-regexp) - (signal 'invalid-regexp - (list (match-string 1 (cadr err)))))))))))) - -(defun pdf-info-pagelinks (page &optional file-or-buffer) - "Return a list of links on PAGE in document FILE-OR-BUFFER. - -This function returns a list of alists with the following keys. -EDGES represents the relative bounding-box of the link , TYPE is -the type of the action, TITLE is a, possibly empty, name for this -action. - -TYPE may be one of - -goto-dest -- This is a internal link to some page. Each element -contains additional keys PAGE and TOP, where PAGE is the page of -the link and TOP its vertical position. - -goto-remote -- This a external link to some document. Same as -goto-dest, with an additional FILENAME of the external PDF. - -uri -- A link in form of some URI. Alist contains additional key -URI. - -In the first two cases, PAGE may be 0 and TOP nil, which means -these data is unspecified." - (cl-check-type page natnum) - (pdf-info-query - 'pagelinks - (pdf-info--normalize-file-or-buffer file-or-buffer) - page)) - -(defun pdf-info-number-of-pages (&optional file-or-buffer) - "Return the number of pages in document FILE-OR-BUFFER." - (pdf-info-query 'number-of-pages - (pdf-info--normalize-file-or-buffer - file-or-buffer))) - -(defun pdf-info-outline (&optional file-or-buffer) - "Return the PDF outline of document FILE-OR-BUFFER. - -This function returns a list of alists like `pdf-info-pagelinks'. -Additionally every alist has a DEPTH (>= 1) entry with the depth -of this element in the tree." - - (pdf-info-query - 'outline - (pdf-info--normalize-file-or-buffer file-or-buffer))) - -(defun pdf-info-gettext (page edges &optional selection-style - file-or-buffer) - "Get text on PAGE according to EDGES. - -EDGES should contain relative coordinates. The selection may -extend over multiple lines, which works similar to a Emacs -region. SELECTION-STYLE may be one of glyph, word or line and -determines the smallest unit of the selected region. - -Return the text contained in the selection." - - (pdf-info-query - 'gettext - (pdf-info--normalize-file-or-buffer file-or-buffer) - page - (mapconcat 'number-to-string edges " ") - (cl-case selection-style - (glyph 0) - (word 1) - (line 2) - (t 0)))) - -(defun pdf-info-getselection (page edges &optional selection-style - file-or-buffer) - "Return the edges of the selection EDGES on PAGE. - -Arguments are the same as for `pdf-info-gettext'. Return a list -of edges corresponding to the text that would be returned by the -aforementioned function, when called with the same arguments." - - (pdf-info-query - 'getselection - (pdf-info--normalize-file-or-buffer file-or-buffer) - page - (mapconcat 'number-to-string edges " ") - (cl-case selection-style - (glyph 0) - (word 1) - (line 2) - (t 0)))) - -(defun pdf-info-textregions (page &optional file-or-buffer) - "Return a list of edges describing PAGE's text-layout." - (pdf-info-getselection - page '(0 0 1 1) 'glyph file-or-buffer)) - -(defun pdf-info-charlayout (page &optional edges-or-pos file-or-buffer) - "Return the layout of characters of PAGE in/at EDGES-OR-POS. - -Returns a list of elements \(CHAR . \(LEFT TOP RIGHT BOT\)\) mapping -character to their corresponding relative bounding-boxes. - -EDGES-OR-POS may be a region \(LEFT TOP RIGHT BOT\) restricting -the returned value to include only characters fully contained in -it. Or a cons \(LEFT . TOP\) which means to only include the -character at this position. In this case the return value -contains at most one element." - - ;; FIXME: Actually returns \(CHAR . LEFT ...\). - - (unless edges-or-pos - (setq edges-or-pos '(0 0 1 1))) - (when (numberp (cdr edges-or-pos)) - (setq edges-or-pos (list (car edges-or-pos) - (cdr edges-or-pos) - -1 -1))) - (pdf-info-query - 'charlayout - (pdf-info--normalize-file-or-buffer file-or-buffer) - page - (mapconcat 'number-to-string edges-or-pos " "))) - -(defun pdf-info-pagesize (page &optional file-or-buffer) - "Return the size of PAGE as a cons \(WIDTH . HEIGHT\) - -The size is in PDF points." - (pdf-info-query - 'pagesize - (pdf-info--normalize-file-or-buffer file-or-buffer) - page)) - -(defun pdf-info-running-p () - "Return non-nil, if the server is running." - (and (processp (pdf-info-process)) - (eq (process-status (pdf-info-process)) - 'run))) - -(defun pdf-info-quit (&optional timeout) - "Quit the epdfinfo server. - -This blocks until all outstanding requests are answered. Unless -TIMEOUT is non-nil, in which case we wait at most TIMEOUT seconds -before killing the server." - (cl-check-type timeout (or null number)) - (when (pdf-info-running-p) - (let ((pdf-info-asynchronous - (if timeout (lambda (&rest _)) - pdf-info-asynchronous))) - (pdf-info-query 'quit) - (when timeout - (setq timeout (+ (float-time) (max 0 timeout))) - (while (and (pdf-info-running-p) - (> timeout (float-time))) - (accept-process-output (pdf-info-process) 0.5 nil t))))) - (when (processp (pdf-info-process)) - (tq-close pdf-info--queue)) - (setq pdf-info--queue nil)) - -(defun pdf-info-kill () - "Kill the epdfinfo server. - -Immediately delete the server process, see also `pdf-info-quit', -for a more sane way to exit the program." - (when (processp (pdf-info-process)) - (tq-close pdf-info--queue)) - (setq pdf-info--queue nil)) - -(defun pdf-info-getannots (&optional pages file-or-buffer) - "Return the annotations on PAGE. - -See `pdf-info-normalize-page-range' for valid PAGES formats. - -This function returns the annotations for PAGES as a list of -alists. Each element of this list describes one annotation and -contains the following keys. - -page - Its page number. -edges - Its area. -type - A symbol describing the annotation's type. -id - A document-wide unique symbol referencing this annotation. -flags - Its flags, binary encoded. -color - Its color in standard Emacs notation. -contents - The text of this annotation. -modified - The last modification date of this annotation. - -Additionally, if the annotation is a markup annotation, the -following keys are present. - -label - The annotation's label. -subject - The subject addressed. -opacity - The level of relative opacity. -popup-edges - The edges of a associated popup window or nil. -popup-is-open - Whether this window should be displayed open. -created - The date this markup annotation was created. - -If the annotation is also a markup text annotation, the alist -contains the following keys. - -text-icon - A string describing the purpose of this annotation. -text-state - A string, e.g. accepted or rejected." ;FIXME: Use symbols ? - - (let ((pages (pdf-info-normalize-page-range pages))) - (pdf-info-query - 'getannots - (pdf-info--normalize-file-or-buffer file-or-buffer) - (car pages) - (cdr pages)))) - -(defun pdf-info-getannot (id &optional file-or-buffer) - "Return the annotation for ID. - -ID should be a symbol, which was previously returned in a -`pdf-info-getannots' query. Signal an error, if an annotation -with ID is not available. - -See `pdf-info-getannots' for the kind of return value of this -function." - (pdf-info-query - 'getannot - (pdf-info--normalize-file-or-buffer file-or-buffer) - id)) - -(defun pdf-info-addannot (page edges type &optional file-or-buffer &rest markup-edges) - "Add a new annotation to PAGE with EDGES of TYPE. - -FIXME: TYPE may be one of `text', `markup-highlight', ... . -FIXME: -1 = 24 -See `pdf-info-getannots' for the kind of value of this function -returns." - (pdf-info-assert-writable-annotations) - (when (consp file-or-buffer) - (push file-or-buffer markup-edges) - (setq file-or-buffer nil)) - (apply - 'pdf-info-query - 'addannot - (pdf-info--normalize-file-or-buffer file-or-buffer) - page - type - (mapconcat 'number-to-string edges " ") - (mapcar (lambda (me) - (mapconcat 'number-to-string me " ")) - markup-edges))) - -(defun pdf-info-delannot (id &optional file-or-buffer) - "Delete the annotation with ID in FILE-OR-BUFFER. - -ID should be a symbol, which was previously returned in a -`pdf-info-getannots' query. Signal an error, if annotation ID -does not exist." - (pdf-info-assert-writable-annotations) - (pdf-info-query - 'delannot - (pdf-info--normalize-file-or-buffer file-or-buffer) - id)) - -(defun pdf-info-mvannot (id edges &optional file-or-buffer) - "Move/Resize annotation ID to fit EDGES. - -ID should be a symbol, which was previously returned in a -`pdf-info-getannots' query. Signal an error, if annotation ID -does not exist. - -EDGES should be a list \(LEFT TOP RIGHT BOT\). RIGHT and/or BOT -may also be negative, which means to keep the width -resp. height." - (pdf-info-editannot id `((edges . ,edges)) file-or-buffer)) - -(defun pdf-info-editannot (id modifications &optional file-or-buffer) - "Edit annotation ID, applying MODIFICATIONS. - -ID should be a symbol, which was previously returned in a -`pdf-info-getannots' query. - -MODIFICATIONS is an alist of properties and their new values. - -The server must support modifying annotations for this to work." - - (pdf-info-assert-writable-annotations) - (let ((edits - (mapcar - (lambda (elt) - (cl-case (car elt) - (color - (list (car elt) - (pdf-util-hexcolor (cdr elt)))) - (edges - (list (car elt) - (mapconcat 'number-to-string (cdr elt) " "))) - ((popup-is-open is-open) - (list (car elt) (if (cdr elt) 1 0))) - (t - (list (car elt) (cdr elt))))) - modifications))) - (apply 'pdf-info-query - 'editannot - (pdf-info--normalize-file-or-buffer file-or-buffer) - id - (apply 'append edits)))) - -(defun pdf-info-save (&optional file-or-buffer) - "Save FILE-OR-BUFFER. - -This saves the document to a new temporary file, which is -returned and owned by the caller." - (pdf-info-assert-writable-annotations) - (pdf-info-query - 'save - (pdf-info--normalize-file-or-buffer file-or-buffer))) - -(defun pdf-info-getattachment-from-annot (id &optional do-save file-or-buffer) - "Return the attachment associated with annotation ID. - -ID should be a symbol which was previously returned in a -`pdf-info-getannots' query, and referencing an attachment of type -`file', otherwise an error is signaled. - -See `pdf-info-getattachments' for the kind of return value of this -function and the meaning of DO-SAVE." - - (pdf-info-query - 'getattachment-from-annot - (pdf-info--normalize-file-or-buffer file-or-buffer) - id - (if do-save 1 0))) - -(defun pdf-info-getattachments (&optional do-save file-or-buffer) - "Return all document level attachments. - -If DO-SAVE is non-nil, save the attachments data to a local file, -which is then owned by the caller, see below. - -This function returns a list of alists, where every element -contains the following keys. All values, except for id, may be -nil, i.e. not present. - -id - A symbol uniquely identifying this attachment. -filename - The filename of this attachment. -description - A description of this attachment. -size - The size in bytes. -modified - The last modification date. -created - The date of creation. -checksum - A MD5 checksum of this attachment's data. -file - The name of a tempfile containing the data (only present if - DO-SAVE is non-nil)." - - (pdf-info-query - 'getattachments - (pdf-info--normalize-file-or-buffer file-or-buffer) - (if do-save 1 0))) - -(defun pdf-info-synctex-forward-search (source &optional line column file-or-buffer) - "Perform a forward search with synctex. - -SOURCE should be a LaTeX buffer or the absolute filename of a -corresponding file. LINE and COLUMN represent the position in -the buffer or file. Finally FILE-OR-BUFFER corresponds to the -PDF document. - -Returns an alist with entries PAGE and relative EDGES describing -the position in the PDF document corresponding to the SOURCE -location." - - (let ((source (if (buffer-live-p (get-buffer source)) - (buffer-file-name (get-buffer source)) - source))) - (pdf-info-query - 'synctex-forward-search - (pdf-info--normalize-file-or-buffer file-or-buffer) - source - (or line 1) - (or column 1)))) - -(defun pdf-info-synctex-backward-search (page &optional x y file-or-buffer) - "Perform a backward search with synctex. - -Find the source location corresponding to the coordinates -\(X . Y\) on PAGE in FILE-OR-BUFFER. - -Returns an alist with entries FILENAME, LINE and COLUMN." - - - (pdf-info-query - 'synctex-backward-search - (pdf-info--normalize-file-or-buffer file-or-buffer) - page - (or x 0) - (or y 0))) - -(defun pdf-info-renderpage (page width &optional file-or-buffer &rest commands) - "Render PAGE with width WIDTH. - -Return the data of the corresponding PNG image." - (when (keywordp file-or-buffer) - (push file-or-buffer commands) - (setq file-or-buffer nil)) - (apply 'pdf-info-query - 'renderpage - (pdf-info--normalize-file-or-buffer file-or-buffer) - page - (* width (pdf-util-frame-scale-factor)) - (let (transformed) - (while (cdr commands) - (let ((kw (pop commands)) - (value (pop commands))) - (setq value - (cl-case kw - ((:crop-to :highlight-line :highlight-region :highlight-text) - (mapconcat 'number-to-string value " ")) - ((:foreground :background) - (pdf-util-hexcolor value)) - (:alpha - (number-to-string value)) - (otherwise value))) - (push kw transformed) - (push value transformed))) - (when commands - (error "Keyword is missing a value: %s" (car commands))) - (nreverse transformed)))) - -(defun pdf-info-renderpage-text-regions (page width single-line-p - &optional file-or-buffer - &rest regions) - "Highlight text on PAGE with width WIDTH using REGIONS. - -REGIONS is a list determining foreground and background color and -the regions to render. So each element should look like \(FG BG -\(LEFT TOP RIGHT BOT\) \(LEFT TOP RIGHT BOT\) ... \) . The -rendering is text-aware. - -If SINGLE-LINE-P is non-nil, the edges in REGIONS are each -supposed to be limited to a single line in the document. Setting -this, if applicable, avoids rendering problems. - -For the other args see `pdf-info-renderpage'. - -Return the data of the corresponding PNG image." - - (when (consp file-or-buffer) - (push file-or-buffer regions) - (setq file-or-buffer nil)) - - (apply 'pdf-info-renderpage - page width file-or-buffer - (apply 'append - (mapcar (lambda (elt) - `(:foreground ,(pop elt) - :background ,(pop elt) - ,@(cl-mapcan (lambda (edges) - `(,(if single-line-p - :highlight-line - :highlight-text) - ,edges)) - elt))) - regions)))) - -(defun pdf-info-renderpage-highlight (page width - &optional file-or-buffer - &rest regions) - "Highlight regions on PAGE with width WIDTH using REGIONS. - -REGIONS is a list determining the background color, a alpha value -and the regions to render. So each element should look like \(FILL-COLOR -STROKE-COLOR ALPHA \(LEFT TOP RIGHT BOT\) \(LEFT TOP RIGHT BOT\) ... \) -. - -For the other args see `pdf-info-renderpage'. - -Return the data of the corresponding PNG image." - - (when (consp file-or-buffer) - (push file-or-buffer regions) - (setq file-or-buffer nil)) - - (apply 'pdf-info-renderpage - page width file-or-buffer - (apply 'append - (mapcar (lambda (elt) - `(:background ,(pop elt) - :foreground ,(pop elt) - :alpha ,(pop elt) - ,@(cl-mapcan (lambda (edges) - `(:highlight-region ,edges)) - elt))) - regions)))) - -(defun pdf-info-boundingbox (page &optional file-or-buffer) - "Return a bounding-box for PAGE. - -Returns a list \(LEFT TOP RIGHT BOT\)." - - (pdf-info-query - 'boundingbox - (pdf-info--normalize-file-or-buffer file-or-buffer) - page)) - -(defun pdf-info-getoptions (&optional file-or-buffer) - (pdf-info-query - 'getoptions - (pdf-info--normalize-file-or-buffer file-or-buffer))) - -(defun pdf-info-setoptions (&optional file-or-buffer &rest options) - (when (symbolp file-or-buffer) - (push file-or-buffer options) - (setq file-or-buffer nil)) - (unless (= (% (length options) 2) 0) - (error "Missing a option value")) - (apply 'pdf-info-query - 'setoptions - (pdf-info--normalize-file-or-buffer file-or-buffer) - (let (soptions) - (while options - (let ((key (pop options)) - (value (pop options))) - (unless (and (keywordp key) - (not (eq key :))) - (error "Keyword expected: %s" key)) - (cl-case key - ((:render/foreground :render/background) - (push (pdf-util-hexcolor value) - soptions)) - ((:render/usecolors :render/printed) - (push (if value 1 0) soptions)) - (t (push value soptions))) - (push key soptions))) - soptions))) - - - -(defun pdf-info-pagelabels (&optional file-or-buffer) - "Return a list of pagelabels. - -Returns a list of strings corresponding to the labels of the -pages in FILE-OR-BUFFER." - - (pdf-info-query - 'pagelabels - (pdf-info--normalize-file-or-buffer file-or-buffer))) - -(defun pdf-info-ping (&optional message) - "Ping the server using MESSAGE. - -Returns MESSAGE, which defaults to \"pong\"." - (pdf-info-query 'ping (or message "pong"))) - -(provide 'pdf-info) - -;;; pdf-info.el ends here -- cgit v1.2.3