Klimi's new dotfiles with stow.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1790 regels
66 KiB

4 jaren geleden
  1. ;;; pdf-annot.el --- Annotation support for PDF files. -*- lexical-binding: t -*-
  2. ;; Copyright (C) 2013, 2014 Andreas Politz
  3. ;; Author: Andreas Politz <politza@fh-trier.de>
  4. ;; Keywords:
  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. ;; This program is distributed in the hope that it will be useful,
  10. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. ;; GNU General Public License for more details.
  13. ;; You should have received a copy of the GNU General Public License
  14. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. ;;; Commentary:
  16. ;;
  17. (require 'pdf-view)
  18. (require 'pdf-info)
  19. (require 'pdf-cache)
  20. (require 'pdf-misc)
  21. (require 'facemenu) ;; list-colors-duplicates
  22. (require 'faces) ;; color-values
  23. (require 'org) ;; org-create-formula-image
  24. (require 'tablist)
  25. (require 'cl-lib)
  26. ;; * ================================================================== *
  27. ;; * Customizations
  28. ;; * ================================================================== *
  29. (defgroup pdf-annot nil
  30. "Annotation support for PDF documents."
  31. :group 'pdf-tools)
  32. (defcustom pdf-annot-activate-handler-functions nil
  33. "A list of functions to activate a annotation.
  34. The functions on this hook will be called when some annotation is
  35. activated, usually by a mouse-click. Each one is called with the
  36. annotation as a single argument and it should return a non-nil
  37. value if it has `handled' it. If no such function exists, the
  38. default handler `pdf-annot-default-handler' will be
  39. called.
  40. This hook is meant to allow for custom annotations. FIXME:
  41. Implement and describe basic org example."
  42. :group 'pdf-annot
  43. :type 'hook)
  44. (defcustom pdf-annot-default-text-annotation-properties nil
  45. "Alist of initial properties for new text annotations."
  46. :group 'pdf-annot
  47. :type '(alist :key-type symbol :value-type sexp))
  48. (defcustom pdf-annot-default-markup-annotation-properties nil
  49. "Alist of initial properties for new markup annotations."
  50. :group 'pdf-annot
  51. :type '(alist :key-type symbol :value-type sexp))
  52. (make-obsolete-variable 'pdf-annot-default-text-annotation-properties
  53. 'pdf-annot-default-annotation-properties
  54. "0.90")
  55. (make-obsolete-variable 'pdf-annot-default-markup-annotation-properties
  56. 'pdf-annot-default-annotation-properties
  57. "0.90")
  58. (defcustom pdf-annot-default-annotation-properties
  59. `((t (label . ,user-full-name))
  60. (text (icon . "Note")
  61. (color . "#ff0000"))
  62. (highlight (color . "yellow"))
  63. (squiggly (color . "orange"))
  64. (strike-out(color . "red"))
  65. (underline (color . "blue")))
  66. "An alist of initial properties for new annotations.
  67. The alist contains a sub-alist for each of the currently available
  68. annotation types, i.e. text, highlight, squiggly, strike-out and
  69. underline. Additionally a sub-alist with a key of t acts as a default
  70. entry.
  71. Each of these sub-alists contain default property-values of newly
  72. added annotations of its respective type.
  73. Some of the most important properties and their types are label
  74. \(a string\), contents \(a string\), color \(a color\) and, for
  75. text-annotations only, icon \(one of the standard icon-types, see
  76. `pdf-annot-standard-text-icons'\).
  77. For example a value of
  78. \(\(t \(color . \"red\"\)
  79. \(label . \"Joe\"\)
  80. \(highlight \(color . \"green\"\)\)
  81. would use a green color for highlight and a red one for other
  82. annotations. Additionally the label for all annotations is set
  83. to \"Joe\"."
  84. :group 'pdf-annot
  85. :type (let* ((label '(cons :tag "Label" (const label) string))
  86. (contents '(cons :tag "Contents" (const contents) string))
  87. (color '(cons :tag "Color" (const color) color))
  88. (icon `(cons :tag "Icon"
  89. (const icon)
  90. (choice
  91. ,@(mapcar (lambda (icon)
  92. `(const ,icon))
  93. '("Note" "Comment" "Key" "Help" "NewParagraph"
  94. "Paragraph" "Insert" "Cross" "Circle")))))
  95. (other '(repeat
  96. :tag "Other properties"
  97. (cons :tag "Property"
  98. (symbol :tag "Key ")
  99. (sexp :tag "Value"))))
  100. (text-properties
  101. `(set ,label ,contents ,color ,icon ,other))
  102. (markup-properties
  103. `(set ,label ,contents ,color))
  104. (all-properties
  105. `(set ,label ,contents ,color ,icon ,other)))
  106. `(set
  107. (cons :tag "All Annotations" (const t) ,all-properties)
  108. (cons :tag "Text Annotations" (const text) ,text-properties)
  109. (cons :tag "Highlight Annotations" (const highlight) ,markup-properties)
  110. (cons :tag "Underline Annotations" (const underline) ,markup-properties)
  111. (cons :tag "Squiggly Annotations" (const squiggly) ,markup-properties)
  112. (cons :tag "Strike-out Annotations" (const strike-out) ,markup-properties))))
  113. (defcustom pdf-annot-print-annotation-functions
  114. '(pdf-annot-print-annotation-latex-maybe)
  115. "A alist of functions for printing annotations, e.g. for the tooltip.
  116. The functions receive the annotation as single argument and
  117. should return either a string or nil. The first string returned
  118. will be used.
  119. If all of them return nil, the default function
  120. `pdf-annot-print-annotation-default' is used."
  121. :group 'pdf-annot
  122. :type 'hook)
  123. (defcustom pdf-annot-latex-string-predicate
  124. (lambda (str)
  125. (and str (string-match "\\`[[:space:]\n]*[$\\]" str)))
  126. "A predicate for recognizing LaTeX fragments.
  127. It receives a string and should return non-nil, if string is a
  128. LaTeX fragment."
  129. :group 'pdf-annot
  130. :type 'function)
  131. (defcustom pdf-annot-latex-header
  132. (concat org-format-latex-header
  133. "\n\\setlength{\\textwidth}{12cm}")
  134. "Header used when latex compiling annotations.
  135. The default value is `org-format-latex-header' + \
  136. \"\\n\\\\setlength{\\\\textwidth}{12cm}\"."
  137. :group 'pdf-annot
  138. :type 'string)
  139. (defcustom pdf-annot-tweak-tooltips t
  140. "Whether this package should tweak some settings regarding tooltips.
  141. If this variable has a non-nil value,
  142. `x-gtk-use-system-tooltips' is set to nil if appropriate, in
  143. order to display text properties;
  144. `tooltip-hide-delay' is set to infinity, in order to not being
  145. annoyed while reading the annotations."
  146. :group 'pdf-annot
  147. :type 'boolean)
  148. (defcustom pdf-annot-activate-created-annotations nil
  149. "Whether to activate (i.e. edit) created annotations."
  150. :group 'pdf-annot
  151. :type 'boolean)
  152. (defcustom pdf-annot-attachment-display-buffer-action nil
  153. "The display action used when displaying attachments."
  154. :group 'pdf-annot
  155. :type display-buffer--action-custom-type)
  156. (defconst pdf-annot-annotation-types
  157. '(3d caret circle file
  158. free-text highlight ink line link movie poly-line polygon popup
  159. printer-mark screen sound square squiggly stamp strike-out text
  160. trap-net underline unknown watermark widget)
  161. "Complete list of annotation types.")
  162. (defcustom pdf-annot-list-listed-types
  163. (if (pdf-info-markup-annotations-p)
  164. (list 'text 'file 'squiggly 'highlight 'underline 'strike-out)
  165. (list 'text 'file))
  166. "A list of annotation types displayed in the list buffer."
  167. :group 'pdf-annot
  168. :type `(set ,@(mapcar (lambda (type)
  169. (list 'const type))
  170. pdf-annot-annotation-types)))
  171. ;; * ================================================================== *
  172. ;; * Variables and Macros
  173. ;; * ================================================================== *
  174. (defvar pdf-annot-color-history nil
  175. "A list of recently used colors for annotations.")
  176. (defvar-local pdf-annot-modified-functions nil
  177. "Functions to call, when an annotation was modified.
  178. A function on this hook should accept one argument: A CLOSURE
  179. containing inserted, changed and deleted annotations.
  180. It may access theses annotations by calling CLOSURE with one of
  181. these arguments:
  182. `:inserted' The list of recently added annotations.
  183. `:deleted' The list of recently deleted annotations.
  184. `:changed' The list of recently changed annotations.
  185. `t' The union of recently added, deleted or changed annotations.
  186. `nil' Just returns nil.
  187. Any other argument signals an error.")
  188. (defconst pdf-annot-text-annotation-size '(24 . 24)
  189. "The Size of text and file annotations in PDF points.
  190. These values are hard-coded in poppler. And while the size of
  191. these annotations may be changed, i.e. the edges property, it has
  192. no effect on the rendering.")
  193. (defconst pdf-annot-markup-annotation-types
  194. '(text link free-text line square
  195. circle polygon poly-line highlight underline squiggly
  196. strike-out stamp caret ink file sound)
  197. "List of defined markup annotation types.")
  198. (defconst pdf-annot-standard-text-icons
  199. '("Note" "Comment" "Key" "Help" "NewParagraph"
  200. "Paragraph" "Insert" "Cross" "Circle")
  201. "A list of standard icon properties for text annotations.")
  202. (defvar pdf-annot-inhibit-modification-hooks nil
  203. "Non-nil, if running `pdf-annot-modified-functions' should be
  204. inhibited after some annotation has changed.")
  205. (defvar-local pdf-annot-delayed-modified-annotations nil
  206. "A plist of not yet propagated modifications.
  207. It contains three entries :change, :delete and :insert. Each one
  208. having a list of annotations as value.")
  209. (defvar-local pdf-annot--attachment-file-alist nil
  210. "Alist mapping attachment ids to unique relative filenames.")
  211. (defmacro pdf-annot-with-atomic-modifications (&rest body)
  212. "Execute BODY joining multiple modifications.
  213. The effect is, that `pdf-annot-modified-functions' will be called
  214. only once at the end of BODY.
  215. BODY should not modify annotations in a different then the
  216. current buffer, because that won't run the hooks properly."
  217. (declare (indent 0) (debug t))
  218. `(unwind-protect
  219. (save-current-buffer
  220. (let ((pdf-annot-inhibit-modification-hooks t))
  221. (progn ,@body)))
  222. (pdf-annot-run-modified-hooks)))
  223. ;; * ================================================================== *
  224. ;; * Minor mode
  225. ;; * ================================================================== *
  226. (defcustom pdf-annot-minor-mode-map-prefix (kbd "C-c C-a")
  227. "The prefix to use for `pdf-annot-minor-mode-map'.
  228. Setting this after the package was loaded has no effect."
  229. :group 'pdf-annot
  230. :type 'key-sequence)
  231. (defvar pdf-annot-minor-mode-map
  232. (let ((kmap (make-sparse-keymap))
  233. (smap (make-sparse-keymap)))
  234. (define-key kmap pdf-annot-minor-mode-map-prefix smap)
  235. (define-key smap "l" 'pdf-annot-list-annotations)
  236. ;; (define-key smap "d" 'pdf-annot-toggle-display-annotations)
  237. (define-key smap "a" 'pdf-annot-attachment-dired)
  238. (when (pdf-info-writable-annotations-p)
  239. (define-key smap "D" 'pdf-annot-delete)
  240. (define-key smap "t" 'pdf-annot-add-text-annotation)
  241. (when (pdf-info-markup-annotations-p)
  242. (define-key smap "m" 'pdf-annot-add-markup-annotation)
  243. (define-key smap "s" 'pdf-annot-add-squiggly-markup-annotation)
  244. (define-key smap "u" 'pdf-annot-add-underline-markup-annotation)
  245. (define-key smap "o" 'pdf-annot-add-strikeout-markup-annotation)
  246. (define-key smap "h" 'pdf-annot-add-highlight-markup-annotation)))
  247. kmap)
  248. "Keymap used for `pdf-annot-minor-mode'.")
  249. (defvar savehist-minibuffer-history-variables)
  250. ;;;###autoload
  251. (define-minor-mode pdf-annot-minor-mode
  252. "Support for PDF Annotations.
  253. \\{pdf-annot-minor-mode-map}"
  254. nil nil nil
  255. (cond
  256. (pdf-annot-minor-mode
  257. (when pdf-annot-tweak-tooltips
  258. (when (boundp 'x-gtk-use-system-tooltips)
  259. (setq x-gtk-use-system-tooltips nil))
  260. (setq tooltip-hide-delay 3600))
  261. (pdf-view-add-hotspot-function 'pdf-annot-hotspot-function 9)
  262. (add-hook 'pdf-info-close-document-hook
  263. 'pdf-annot-attachment-delete-base-directory nil t)
  264. (when (featurep 'savehist)
  265. (add-to-list 'savehist-minibuffer-history-variables
  266. 'pdf-annot-color-history)))
  267. (t
  268. (pdf-view-remove-hotspot-function 'pdf-annot-hotspot-function)
  269. (remove-hook 'pdf-info-close-document-hook
  270. 'pdf-annot-attachment-delete-base-directory t)))
  271. (pdf-view-redisplay t))
  272. (defun pdf-annot-create-context-menu (a)
  273. "Create a appropriate context menu for annotation A."
  274. (let ((menu (make-sparse-keymap)))
  275. ;; (when (and (bound-and-true-p pdf-misc-menu-bar-minor-mode)
  276. ;; (bound-and-true-p pdf-misc-install-popup-menu))
  277. ;; (set-keymap-parent menu
  278. ;; (lookup-key pdf-misc-menu-bar-minor-mode-map
  279. ;; [menu-bar pdf-tools]))
  280. ;; (define-key menu [sep-99] menu-bar-separator))
  281. (when (pdf-info-writable-annotations-p)
  282. (define-key menu [delete-annotation]
  283. `(menu-item "Delete annotation"
  284. ,(lambda ()
  285. (interactive)
  286. (pdf-annot-delete a)
  287. (message "Annotation deleted"))
  288. :help
  289. "Delete this annotation.")))
  290. (define-key menu [goto-annotation]
  291. `(menu-item "List annotation"
  292. ,(lambda ()
  293. (interactive)
  294. (pdf-annot-show-annotation a t)
  295. (pdf-annot-list-annotations)
  296. (pdf-annot-list-goto-annotation a))
  297. :help "Find this annotation in the list buffer."))
  298. (when (pdf-annot-text-annotation-p a)
  299. (define-key menu [change-text-icon]
  300. `(menu-item "Change icon"
  301. ,(pdf-annot-create-icon-submenu a)
  302. :help "Change the appearance of this annotation.")))
  303. (define-key menu [change-color]
  304. `(menu-item "Change color"
  305. ,(pdf-annot-create-color-submenu a)
  306. :help "Change the appearance of this annotation."))
  307. (define-key menu [activate-annotation]
  308. `(menu-item "Activate"
  309. ,(lambda ()
  310. (interactive)
  311. (pdf-annot-activate-annotation a))
  312. :help "Activate this annotation."))
  313. menu))
  314. (defun pdf-annot-create-color-submenu (a)
  315. (let ((menu (make-sparse-keymap)))
  316. (define-key menu [color-chooser]
  317. `(menu-item "Choose ..."
  318. ,(lambda ()
  319. (interactive)
  320. (list-colors-display
  321. nil "*Choose annotation color*"
  322. ;; list-colors-print does not like closures.
  323. (let ((callback (make-symbol "xcallback")))
  324. (fset callback
  325. (lambda (color)
  326. (pdf-annot-put a 'color color)
  327. (setq pdf-annot-color-history
  328. (cons color
  329. (remove color pdf-annot-color-history)))
  330. (quit-window t)))
  331. (list 'function callback))))))
  332. (dolist (color (butlast (reverse pdf-annot-color-history)
  333. (max 0 (- (length pdf-annot-color-history)
  334. 12))))
  335. (define-key menu (vector (intern (format "color-%s" color)))
  336. `(menu-item ,color
  337. ,(lambda nil
  338. (interactive)
  339. (pdf-annot-put a 'color color)))))
  340. menu))
  341. (defun pdf-annot-create-icon-submenu (a)
  342. (let ((menu (make-sparse-keymap)))
  343. (dolist (icon (reverse pdf-annot-standard-text-icons))
  344. (define-key menu (vector (intern (format "icon-%s" icon)))
  345. `(menu-item ,icon
  346. ,(lambda nil
  347. (interactive)
  348. (pdf-annot-put a 'icon icon)))))
  349. menu))
  350. ;; * ================================================================== *
  351. ;; * Annotation Basics
  352. ;; * ================================================================== *
  353. (defun pdf-annot-create (alist &optional buffer)
  354. "Create a annotation from ALIST in BUFFER.
  355. ALIST should be a property list as returned by
  356. `pdf-cache-getannots'. BUFFER should be the buffer of the
  357. corresponding PDF document. It defaults to the current buffer."
  358. (cons `(buffer . ,(or buffer (current-buffer)))
  359. alist))
  360. (defun pdf-annot-getannots (&optional pages types buffer)
  361. "Return a list of annotations on PAGES of TYPES in BUFFER.
  362. See `pdf-info-normalize-pages' for valid values of PAGES. TYPES
  363. may be a symbol or list of symbols denoting annotation types.
  364. PAGES defaults to all pages, TYPES to all types and BUFFER to the
  365. current buffer."
  366. (pdf-util-assert-pdf-buffer buffer)
  367. (unless buffer
  368. (setq buffer (current-buffer)))
  369. (unless (listp types)
  370. (setq types (list types)))
  371. (with-current-buffer buffer
  372. (let (result)
  373. (dolist (a (pdf-info-getannots pages))
  374. (when (or (null types)
  375. (memq (pdf-annot-get a 'type) types))
  376. (push (pdf-annot-create a) result)))
  377. result)))
  378. (defun pdf-annot-getannot (id &optional buffer)
  379. (pdf-annot-create
  380. (pdf-info-getannot id buffer)
  381. buffer))
  382. (defun pdf-annot-get (a property &optional default)
  383. "Get annotation A's value of PROPERTY.
  384. Return DEFAULT, if value is nil."
  385. (or (cdr (assq property a)) default))
  386. (defun pdf-annot-put (a property value)
  387. "Set annotation A's PROPERTY to VALUE.
  388. Unless VALUE is `equal' to the current value, sets A's buffer's
  389. modified flag and runs the hook `pdf-annot-modified-functions'.
  390. Signals an error, if PROPERTY is not modifiable.
  391. Returns the modified annotation."
  392. (declare (indent 2))
  393. (unless (equal value (pdf-annot-get a property))
  394. (unless (pdf-annot-property-modifiable-p a property)
  395. (error "Property `%s' is read-only for this annotation"
  396. property))
  397. (with-current-buffer (pdf-annot-get-buffer a)
  398. (setq a (pdf-annot-create
  399. (pdf-info-editannot
  400. (pdf-annot-get-id a)
  401. `((,property . ,value)))))
  402. (set-buffer-modified-p t)
  403. (pdf-annot-run-modified-hooks :change a)))
  404. a)
  405. (defun pdf-annot-run-modified-hooks (&optional operation &rest annotations)
  406. "Run `pdf-annot-modified-functions' using OPERATION on ANNOTATIONS.
  407. OPERATION should be one of nil, :change, :insert or :delete. If
  408. nil, annotations should be empty.
  409. Redisplay modified pages.
  410. If `pdf-annot-inhibit-modification-hooks' in non-nil, this just
  411. saves ANNOTATIONS and does not call the hooks until later, when
  412. the variable is nil and this function is called again."
  413. (unless (memq operation '(nil :insert :change :delete))
  414. (error "Invalid operation: %s" operation))
  415. (when (and (null operation) annotations)
  416. (error "Missing operation argument"))
  417. (when operation
  418. (let ((list (plist-get pdf-annot-delayed-modified-annotations operation)))
  419. (dolist (a annotations)
  420. (cl-pushnew a list :test 'pdf-annot-equal))
  421. (setq pdf-annot-delayed-modified-annotations
  422. (plist-put pdf-annot-delayed-modified-annotations
  423. operation list))))
  424. (unless pdf-annot-inhibit-modification-hooks
  425. (let* ((changed (plist-get pdf-annot-delayed-modified-annotations :change))
  426. (inserted (mapcar (lambda (a)
  427. (or (car (cl-member a changed :test 'pdf-annot-equal))
  428. a))
  429. (plist-get pdf-annot-delayed-modified-annotations :insert)))
  430. (deleted (plist-get pdf-annot-delayed-modified-annotations :delete))
  431. (union (cl-union (cl-union changed inserted :test 'pdf-annot-equal)
  432. deleted :test 'pdf-annot-equal))
  433. (closure (lambda (arg)
  434. (cl-ecase arg
  435. (:inserted (copy-sequence inserted))
  436. (:changed (copy-sequence changed))
  437. (:deleted (copy-sequence deleted))
  438. (t (copy-sequence union))
  439. (nil nil))))
  440. (pages (mapcar (lambda (a) (pdf-annot-get a 'page)) union)))
  441. (when union
  442. (unwind-protect
  443. (run-hook-with-args
  444. 'pdf-annot-modified-functions closure)
  445. (setq pdf-annot-delayed-modified-annotations nil)
  446. (apply 'pdf-view-redisplay-pages pages))))))
  447. (defun pdf-annot-equal (a1 a2)
  448. "Return non-nil, if annotations A1 and A2 are equal.
  449. Two annotations are equal, if they belong to the same buffer and
  450. have identical id properties."
  451. (and (eq (pdf-annot-get-buffer a1)
  452. (pdf-annot-get-buffer a2))
  453. (eq (pdf-annot-get-id a1)
  454. (pdf-annot-get-id a2))))
  455. (defun pdf-annot-get-buffer (a)
  456. "Return annotation A's buffer."
  457. (pdf-annot-get a 'buffer))
  458. (defun pdf-annot-get-id (a)
  459. "Return id property of annotation A."
  460. (pdf-annot-get a 'id))
  461. (defun pdf-annot-get-type (a)
  462. "Return type property of annotation A."
  463. (pdf-annot-get a 'type))
  464. (defun pdf-annot-get-display-edges (a)
  465. "Return a list of EDGES used for display for annotation A.
  466. This returns a list of \(LEFT TOP RIGHT BOT\) demarking the
  467. rectangles of the page where A is rendered."
  468. (or (pdf-annot-get a 'markup-edges)
  469. (list (pdf-annot-get a 'edges))))
  470. (defun pdf-annot-delete (a)
  471. "Delete annotation A.
  472. Sets A's buffer's modified flag and runs the hook
  473. `pdf-annot-modified-functions'.
  474. This function always returns nil."
  475. (interactive
  476. (list (pdf-annot-read-annotation
  477. "Click on the annotation you wish to delete")))
  478. (with-current-buffer (pdf-annot-get-buffer a)
  479. (pdf-info-delannot
  480. (pdf-annot-get-id a))
  481. (set-buffer-modified-p t)
  482. (pdf-annot-run-modified-hooks :delete a))
  483. (when (called-interactively-p 'any)
  484. (message "Annotation deleted"))
  485. nil)
  486. (defun pdf-annot-text-annotation-p (a)
  487. (eq 'text (pdf-annot-get a 'type)))
  488. (defun pdf-annot-markup-annotation-p (a)
  489. (not (null
  490. (memq (pdf-annot-get a 'type)
  491. pdf-annot-markup-annotation-types))))
  492. (defun pdf-annot-property-modifiable-p (a property)
  493. (or (memq property '(edges color flags contents))
  494. (and (pdf-annot-markup-annotation-p a)
  495. (memq property '(label opacity popup popup-is-open)))
  496. (and (pdf-annot-text-annotation-p a)
  497. (memq property '(icon is-open)))))
  498. (defun pdf-annot-activate-annotation (a)
  499. (or (run-hook-with-args-until-success
  500. 'pdf-annot-activate-handler-functions
  501. a)
  502. (pdf-annot-default-activate-handler a)))
  503. (defun pdf-annot-default-activate-handler (a)
  504. (cond
  505. ((pdf-annot-has-attachment-p a)
  506. (pdf-annot-pop-to-attachment a))
  507. (t (pdf-annot-edit-contents a))))
  508. ;; * ================================================================== *
  509. ;; * Handling attachments
  510. ;; * ================================================================== *
  511. (defun pdf-annot-has-attachment-p (a)
  512. "Return non-nil if annotation A's has data attached."
  513. (eq 'file (pdf-annot-get a 'type)))
  514. (defun pdf-annot-get-attachment (a &optional do-save)
  515. "Retrieve annotation A's attachment.
  516. The DO-SAVE argument is given to
  517. `pdf-info-getattachment-from-annot', which see."
  518. (unless (pdf-annot-has-attachment-p a)
  519. (error "Annotation has no data attached: %s" a))
  520. (pdf-info-getattachment-from-annot
  521. (pdf-annot-get-id a)
  522. do-save
  523. (pdf-annot-get-buffer a)))
  524. (defun pdf-annot-attachment-base-directory ()
  525. "Return the base directory for saving attachments."
  526. (let ((dir (pdf-util-expand-file-name "attachments")))
  527. (unless (file-exists-p dir)
  528. (make-directory dir))
  529. dir))
  530. (defun pdf-annot-attachment-delete-base-directory ()
  531. "Delete all saved attachment files of the current buffer."
  532. (setq pdf-annot--attachment-file-alist nil)
  533. (delete-directory (pdf-annot-attachment-base-directory) t))
  534. (defun pdf-annot-attachment-unique-filename (attachment)
  535. "Return a unique absolute filename for ATTACHMENT."
  536. (let* ((filename (or (cdr (assq 'filename attachment))
  537. "attachment"))
  538. (id (cdr (assq 'id attachment)))
  539. (unique
  540. (or (cdr (assoc id pdf-annot--attachment-file-alist))
  541. (let* ((sans-ext
  542. (expand-file-name
  543. (concat (file-name-as-directory ".")
  544. (file-name-sans-extension filename))
  545. (pdf-annot-attachment-base-directory)))
  546. (ext (file-name-extension filename))
  547. (newname (concat sans-ext "." ext))
  548. (i 0))
  549. (while (rassoc newname pdf-annot--attachment-file-alist)
  550. (setq newname (format "%s-%d.%s" sans-ext (cl-incf i) ext)))
  551. (push (cons id newname) pdf-annot--attachment-file-alist)
  552. newname)))
  553. (directory (file-name-directory unique)))
  554. (unless (file-exists-p directory)
  555. (make-directory directory t))
  556. unique))
  557. (defun pdf-annot-attachment-save (attachment &optional regenerate-p)
  558. "Save ATTACHMENT's data to a unique filename and return it's name.
  559. If REGENERATE-P is non-nil, copy attachment's file even if the
  560. copy already exists.
  561. Signal an error, if ATTACHMENT has no, or a non-existing, `file'
  562. property, i.e. it was retrieved with an unset do-save argument.
  563. See `pdf-info-getattachments'"
  564. (let ((datafile (cdr (assq 'file attachment))))
  565. (unless (and datafile
  566. (file-exists-p datafile))
  567. (error "Attachment's file property is invalid"))
  568. (let* ((filename
  569. (pdf-annot-attachment-unique-filename attachment)))
  570. (when (or regenerate-p
  571. (not (file-exists-p filename)))
  572. (copy-file datafile filename nil nil t t))
  573. filename)))
  574. (defun pdf-annot-find-attachment-noselect (a)
  575. "Find annotation A's attachment in a buffer, without selecting it.
  576. Signals an error, if A has no data attached."
  577. (let ((attachment (pdf-annot-get-attachment a t)))
  578. (unwind-protect
  579. (find-file-noselect
  580. (pdf-annot-attachment-save attachment))
  581. (let ((tmpfile (cdr (assq 'file attachment))))
  582. (when (and tmpfile
  583. (file-exists-p tmpfile))
  584. (delete-file tmpfile))))))
  585. (defun pdf-annot-attachment-dired (&optional regenerate-p)
  586. "List all attachments in a dired buffer.
  587. If REGENERATE-P is non-nil, create attachment's files even if
  588. they already exist. Interactively REGENERATE-P is non-nil if a
  589. prefix argument was given.
  590. Return the dired buffer."
  591. (interactive (list current-prefix-arg))
  592. (let ((attachments (pdf-info-getattachments t)))
  593. (unwind-protect
  594. (progn
  595. (dolist (a (pdf-annot-getannots nil 'file))
  596. (push (pdf-annot-get-attachment a t)
  597. attachments ))
  598. (dolist (att attachments)
  599. (pdf-annot-attachment-save att regenerate-p))
  600. (unless attachments
  601. (error "Document has no data attached"))
  602. (dired (pdf-annot-attachment-base-directory)))
  603. (dolist (att attachments)
  604. (let ((tmpfile (cdr (assq 'file att))))
  605. (when (and tmpfile (file-exists-p tmpfile))
  606. (delete-file tmpfile)))))))
  607. (defun pdf-annot-display-attachment (a &optional display-action select-window-p)
  608. "Display file annotation A's data in a buffer.
  609. DISPLAY-ACTION should be a valid `display-buffer' action. If
  610. nil, `pdf-annot-attachment-display-buffer-action' is used.
  611. Select the window, if SELECT-WINDOW-P is non-nil.
  612. Return the window attachment is displayed in."
  613. (interactive
  614. (list (pdf-annot-read-annotation
  615. "Select a file annotation by clicking on it")))
  616. (let* ((buffer (pdf-annot-find-attachment-noselect a))
  617. (window (display-buffer
  618. buffer (or display-action
  619. pdf-annot-attachment-display-buffer-action))))
  620. (when select-window-p
  621. (select-window window))
  622. window))
  623. (defun pdf-annot-pop-to-attachment (a)
  624. "Display annotation A's attachment in a window and select it."
  625. (interactive
  626. (list (pdf-annot-read-annotation
  627. "Select a file annotation by clicking on it")))
  628. (pdf-annot-display-attachment a nil t))
  629. ;; * ================================================================== *
  630. ;; * Interfacing with the display
  631. ;; * ================================================================== *
  632. (defun pdf-annot-image-position (a &optional image-size)
  633. "Return the position of annotation A in image coordinates.
  634. IMAGE-SIZE should be a cons \(WIDTH . HEIGHT\) and defaults to
  635. the page-image of the selected window."
  636. (unless image-size
  637. (pdf-util-assert-pdf-window)
  638. (setq image-size (pdf-view-image-size)))
  639. (let ((e (pdf-util-scale
  640. (pdf-annot-get a 'edges)
  641. image-size)))
  642. (pdf-util-with-edges (e)
  643. `(,e-left . ,e-top))))
  644. (defun pdf-annot-image-set-position (a x y &optional image-size)
  645. "Set annotation A's position to X,Y in image coordinates.
  646. See `pdf-annot-image-position' for IMAGE-SIZE."
  647. (unless image-size
  648. (pdf-util-assert-pdf-window)
  649. (setq image-size (pdf-view-image-size)))
  650. (let* ((edges (pdf-annot-get a 'edges))
  651. (x (/ x (float (car image-size))))
  652. (y (/ y (float (cdr image-size)))))
  653. (pdf-util-with-edges (edges)
  654. (let* ((w edges-width)
  655. (h edges-height)
  656. (x (max 0 (min x (- 1 w))))
  657. (y (max 0 (min y (- 1 h)))))
  658. (pdf-annot-put a 'edges
  659. (list x y -1 -1))))))
  660. (defun pdf-annot-image-size (a &optional image-size)
  661. "Return the size of annotation A in image coordinates.
  662. Returns \(WIDTH . HEIGHT\).
  663. See `pdf-annot-image-position' for IMAGE-SIZE."
  664. (unless image-size
  665. (pdf-util-assert-pdf-window)
  666. (setq image-size (pdf-view-image-size)))
  667. (let ((edges (pdf-util-scale
  668. (pdf-annot-get a 'edges) image-size)))
  669. (pdf-util-with-edges (edges)
  670. (cons edges-width edges-height))))
  671. (defun pdf-annot-image-set-size (a &optional width height image-size)
  672. "Set annotation A's size in image to WIDTH and/or HEIGHT.
  673. See `pdf-annot-image-position' for IMAGE-SIZE."
  674. (unless image-size
  675. (pdf-util-assert-pdf-window)
  676. (setq image-size (pdf-view-image-size)))
  677. (let* ((edges (pdf-annot-get a 'edges))
  678. (w (and width
  679. (/ width (float (car image-size)))))
  680. (h (and height
  681. (/ height (float (cdr image-size))))))
  682. (pdf-util-with-edges (edges)
  683. (pdf-annot-put a 'edges
  684. (list edges-left
  685. edges-top
  686. (if w (+ edges-left w) edges-right)
  687. (if h (+ edges-top h) edges-bot))))))
  688. (defun pdf-annot-at-position (pos)
  689. "Return annotation at POS in the selected window.
  690. POS should be an absolute image position as a cons \(X . Y\).
  691. Alternatively POS may also be an event position, in which case
  692. `posn-window' and `posn-object-x-y' is used to find the image
  693. position.
  694. Return nil, if no annotation was found."
  695. (let (window)
  696. (when (posnp pos)
  697. (setq window (posn-window pos)
  698. pos (posn-object-x-y pos)))
  699. (save-selected-window
  700. (when window (select-window window))
  701. (let* ((annots (pdf-annot-getannots (pdf-view-current-page)))
  702. (size (pdf-view-image-size))
  703. (rx (/ (car pos) (float (car size))))
  704. (ry (/ (cdr pos) (float (cdr size))))
  705. (rpos (cons rx ry)))
  706. (or (cl-some (lambda (a)
  707. (and (cl-some
  708. (lambda (e)
  709. (pdf-util-edges-inside-p e rpos))
  710. (pdf-annot-get-display-edges a))
  711. a))
  712. annots)
  713. (error "No annotation at this position"))))))
  714. (defun pdf-annot-mouse-move (event &optional annot)
  715. "Start moving an annotation at EVENT's position.
  716. EVENT should be a mouse event originating the request and is used
  717. as a reference point.
  718. ANNOT is the annotation to operate on and defaults to the
  719. annotation at EVENT's start position.
  720. This function does not return until the operation is completed,
  721. i.e. a non mouse-movement event is read."
  722. (interactive "@e")
  723. (pdf-util-assert-pdf-window (posn-window (event-start event)))
  724. (select-window (posn-window (event-start event)))
  725. (let* ((mpos (posn-object-x-y (event-start event)))
  726. (a (or annot
  727. (pdf-annot-at-position mpos))))
  728. (unless a
  729. (error "No annotation at this position: %s" mpos))
  730. (let* ((apos (pdf-annot-image-position a))
  731. (offset (cons (- (car mpos) (car apos))
  732. (- (cdr mpos) (cdr apos))))
  733. (window (selected-window))
  734. make-pointer-invisible)
  735. (when (pdf-util-track-mouse-dragging (ev 0.1)
  736. (when (and (eq window (posn-window (event-start ev)))
  737. (eq 'image (car-safe (posn-object (event-start ev)))))
  738. (let ((pdf-view-inhibit-hotspots t)
  739. (pdf-annot-inhibit-modification-hooks t)
  740. (pdf-cache-image-inihibit t)
  741. (xy (posn-object-x-y (event-start ev))))
  742. (pdf-annot-image-set-position
  743. a (- (car xy) (car offset))
  744. (- (cdr xy) (cdr offset)))
  745. (pdf-view-redisplay))))
  746. (pdf-annot-run-modified-hooks)))
  747. nil))
  748. (defun pdf-annot-hotspot-function (page size)
  749. "Create image hotspots for page PAGE of size SIZE."
  750. (apply 'nconc (mapcar (lambda (a)
  751. (unless (eq (pdf-annot-get a 'type)
  752. 'link)
  753. (pdf-annot-create-hotspots a size)))
  754. (pdf-annot-getannots page))))
  755. (defun pdf-annot-create-hotspots (a size)
  756. "Return a list of image hotspots for annotation A."
  757. (let ((id (pdf-annot-get-id a))
  758. (edges (pdf-util-scale
  759. (pdf-annot-get-display-edges a)
  760. size 'round))
  761. (moveable-p (memq (pdf-annot-get a 'type)
  762. '(file text)))
  763. hotspots)
  764. (dolist (e edges)
  765. (pdf-util-with-edges (e)
  766. (push `((rect . ((,e-left . ,e-top) . (,e-right . ,e-bot)))
  767. ,id
  768. (pointer
  769. hand
  770. help-echo
  771. ,(pdf-annot-print-annotation a)))
  772. hotspots)))
  773. (pdf-annot-create-hotspot-binding id moveable-p a)
  774. hotspots))
  775. ;; FIXME: Define a keymap as a template for this. Much cleaner.
  776. (defun pdf-annot-create-hotspot-binding (id moveable-p annotation)
  777. ;; Activating
  778. (local-set-key
  779. (vector id 'mouse-1)
  780. (lambda ()
  781. (interactive)
  782. (pdf-annot-activate-annotation annotation)))
  783. ;; Move
  784. (when moveable-p
  785. (local-set-key
  786. (vector id 'down-mouse-1)
  787. (lambda (ev)
  788. (interactive "@e")
  789. (pdf-annot-mouse-move ev annotation))))
  790. ;; Context Menu
  791. (local-set-key
  792. (vector id 'down-mouse-3)
  793. (lambda ()
  794. (interactive "@")
  795. (popup-menu (pdf-annot-create-context-menu annotation))))
  796. ;; Everything else
  797. (local-set-key
  798. (vector id t)
  799. 'pdf-util-image-map-mouse-event-proxy))
  800. (defun pdf-annot-show-annotation (a &optional highlight-p window)
  801. "Make annotation A visible.
  802. Turn to A's page in WINDOW, and scroll it if necessary.
  803. If HIGHLIGHT-P is non-nil, visually distinguish annotation A from
  804. other annotations."
  805. (save-selected-window
  806. (when window (select-window window))
  807. (pdf-util-assert-pdf-window)
  808. (let ((page (pdf-annot-get a 'page))
  809. (size (pdf-view-image-size)))
  810. (unless (= page (pdf-view-current-page))
  811. (pdf-view-goto-page page))
  812. (let ((edges (pdf-annot-get-display-edges a)))
  813. (when highlight-p
  814. (pdf-view-display-image
  815. (pdf-view-create-image
  816. (pdf-cache-renderpage-highlight
  817. page (car size)
  818. `("white" "steel blue" 0.35 ,@edges))
  819. :map (pdf-view-apply-hotspot-functions
  820. window page size))))
  821. (pdf-util-scroll-to-edges
  822. (pdf-util-scale-relative-to-pixel (car edges)))))))
  823. (defun pdf-annot-read-annotation (&optional prompt)
  824. "Let the user choose a annotation a mouse click using PROMPT."
  825. (pdf-annot-at-position
  826. (pdf-util-read-image-position
  827. (or prompt "Choose a annotation by clicking on it"))))
  828. ;; * ================================================================== *
  829. ;; * Creating annotations
  830. ;; * ================================================================== *
  831. (defun pdf-annot-add-annotation (type edges &optional property-alist page)
  832. "Creates and adds a new annotation of type TYPE to the document.
  833. TYPE determines the kind of annotation to add and maybe one of
  834. `text', `squiggly', `underline', `strike-out' or `highlight'.
  835. EDGES determines where the annotation will appear on the page.
  836. If type is `text', this should be a single list of \(LEFT TOP
  837. RIGHT BOT\). Though, in this case only LEFT and TOP are used,
  838. since the size of text annotations is fixed. Otherwise EDGES may
  839. be a list of such elements. All values should be image relative
  840. coordinates, i.e. in the range \[0;1\].
  841. PROPERTY-ALIST is a list of annotation properties, which will be
  842. put on the created annotation.
  843. PAGE determines the page of the annotation. It defaults to the
  844. page currently displayed in the selected window.
  845. Signal an error, if PROPERTY-ALIST contains non-modifiable
  846. properties or PAGE is nil and the selected window does not
  847. display a PDF document or creating annotations of type TYPE is
  848. not supported.
  849. Set buffers modified flag and calls
  850. `pdf-annot-activate-annotation' if
  851. `pdf-annot-activate-created-annotations' is non-nil.
  852. Return the new annotation."
  853. (unless (memq type (pdf-info-creatable-annotation-types))
  854. (error "Unsupported annotation type: %s" type))
  855. (unless page
  856. (pdf-util-assert-pdf-window)
  857. (setq page (pdf-view-current-page)))
  858. (unless (consp (car-safe edges))
  859. (setq edges (list edges)))
  860. (when (and (eq type 'text)
  861. (> (length edges) 1))
  862. (error "Edges argument should be a single edge-list for text annotations"))
  863. (let* ((a (apply 'pdf-info-addannot
  864. page
  865. (if (eq type 'text)
  866. (car edges)
  867. (apply #'pdf-util-edges-union
  868. (apply #'append
  869. (mapcar
  870. (lambda (e)
  871. (pdf-info-getselection page e))
  872. edges))))
  873. type
  874. nil
  875. (if (not (eq type 'text)) edges)))
  876. (id (pdf-annot-get-id a)))
  877. (when property-alist
  878. (condition-case err
  879. (setq a (pdf-info-editannot id property-alist))
  880. (error
  881. (pdf-info-delannot id)
  882. (signal (car err) (cdr err)))))
  883. (setq a (pdf-annot-create a))
  884. (set-buffer-modified-p t)
  885. (pdf-annot-run-modified-hooks :insert a)
  886. (when pdf-annot-activate-created-annotations
  887. (pdf-annot-activate-annotation a))
  888. a))
  889. (defun pdf-annot-add-text-annotation (pos &optional icon property-alist)
  890. "Add a new text annotation at POS in the selected window.
  891. POS should be a image position object or a cons \(X . Y\), both
  892. being image coordinates.
  893. ICON determines how the annotation is displayed and should be
  894. listed in `pdf-annot-standard-text-icons'. Any other value is ok
  895. as well, but will render the annotation invisible.
  896. Adjust X and Y accordingly, if the position would render the
  897. annotation off-page.
  898. Merge ICON as a icon property with PROPERTY-ALIST and
  899. `pdf-annot-default-text-annotation-properties' and apply the
  900. result to the created annotation.
  901. See also `pdf-annot-add-annotation'.
  902. Return the new annotation."
  903. (interactive
  904. (let* ((posn (pdf-util-read-image-position
  905. "Click where a new text annotation should be added ..."))
  906. (window (posn-window posn)))
  907. (select-window window)
  908. (list posn)))
  909. (pdf-util-assert-pdf-window)
  910. (when (posnp pos)
  911. (setq pos (posn-object-x-y pos)))
  912. (let ((isize (pdf-view-image-size))
  913. (x (car pos))
  914. (y (cdr pos)))
  915. (unless (and (>= x 0)
  916. (< x (car isize)))
  917. (signal 'args-out-of-range (list pos)))
  918. (unless (and (>= y 0)
  919. (< y (cdr isize)))
  920. (signal 'args-out-of-range (list pos)))
  921. (let ((size (pdf-util-scale-points-to-pixel
  922. pdf-annot-text-annotation-size 'round)))
  923. (setcar size (min (car size) (car isize)))
  924. (setcdr size (min (cdr size) (cdr isize)))
  925. (cl-decf x (max 0 (- (+ x (car size)) (car isize))))
  926. (cl-decf y (max 0 (- (+ y (cdr size)) (cdr isize))))
  927. (pdf-annot-add-annotation
  928. 'text (pdf-util-scale-pixel-to-relative
  929. (list x y -1 -1))
  930. (pdf-annot-merge-alists
  931. (and icon `((icon . ,icon)))
  932. property-alist
  933. pdf-annot-default-text-annotation-properties
  934. (cdr (assq 'text pdf-annot-default-annotation-properties))
  935. (cdr (assq t pdf-annot-default-annotation-properties))
  936. `((color . ,(car pdf-annot-color-history))))))))
  937. (defun pdf-annot-mouse-add-text-annotation (ev)
  938. (interactive "@e")
  939. (pdf-annot-add-text-annotation
  940. (if (eq (car-safe ev)
  941. 'menu-bar)
  942. (let (echo-keystrokes)
  943. (message nil)
  944. (pdf-util-read-image-position
  945. "Click where a new text annotation should be added ..."))
  946. (event-start ev))))
  947. (defun pdf-annot-add-markup-annotation (list-of-edges type &optional color
  948. property-alist)
  949. "Add a new markup annotation in the selected window.
  950. LIST-OF-EDGES determines the marked up area and should be a list
  951. of \(LEFT TOP RIGHT BOT\), each value a relative coordinate.
  952. TYPE should be one of `squiggly', `underline', `strike-out' or
  953. `highlight'.
  954. Merge COLOR as a color property with PROPERTY-ALIST and
  955. `pdf-annot-default-markup-annotation-properties' and apply the
  956. result to the created annotation.
  957. See also `pdf-annot-add-annotation'.
  958. Return the new annotation."
  959. (interactive
  960. (list (pdf-view-active-region t)
  961. (let ((type (completing-read "Markup type (default highlight): "
  962. '("squiggly" "highlight" "underline" "strike-out")
  963. nil t)))
  964. (if (equal type "") 'highlight (intern type)))
  965. (pdf-annot-read-color)))
  966. (pdf-util-assert-pdf-window)
  967. (pdf-annot-add-annotation
  968. type
  969. list-of-edges
  970. (pdf-annot-merge-alists
  971. (and color `((color . ,color)))
  972. property-alist
  973. pdf-annot-default-markup-annotation-properties
  974. (cdr (assq type pdf-annot-default-annotation-properties))
  975. (cdr (assq t pdf-annot-default-annotation-properties))
  976. (when pdf-annot-color-history
  977. `((color . ,(car pdf-annot-color-history))))
  978. '((color . "#ffff00")))
  979. (pdf-view-current-page)))
  980. (defun pdf-annot-add-squiggly-markup-annotation (list-of-edges
  981. &optional color property-alist)
  982. "Add a new squiggly annotation in the selected window.
  983. See also `pdf-annot-add-markup-annotation'."
  984. (interactive (list (pdf-view-active-region t)))
  985. (pdf-annot-add-markup-annotation list-of-edges 'squiggly color property-alist))
  986. (defun pdf-annot-add-underline-markup-annotation (list-of-edges
  987. &optional color property-alist)
  988. "Add a new underline annotation in the selected window.
  989. See also `pdf-annot-add-markup-annotation'."
  990. (interactive (list (pdf-view-active-region t)))
  991. (pdf-annot-add-markup-annotation list-of-edges 'underline color property-alist))
  992. (defun pdf-annot-add-strikeout-markup-annotation (list-of-edges
  993. &optional color property-alist)
  994. "Add a new strike-out annotation in the selected window.
  995. See also `pdf-annot-add-markup-annotation'."
  996. (interactive (list (pdf-view-active-region t)))
  997. (pdf-annot-add-markup-annotation list-of-edges 'strike-out color property-alist))
  998. (defun pdf-annot-add-highlight-markup-annotation (list-of-edges
  999. &optional color property-alist)
  1000. "Add a new highlight annotation in the selected window.
  1001. See also `pdf-annot-add-markup-annotation'."
  1002. (interactive (list (pdf-view-active-region t)))
  1003. (pdf-annot-add-markup-annotation list-of-edges 'highlight color property-alist))
  1004. (defun pdf-annot-read-color (&optional prompt)
  1005. "Read and return a color using PROMPT.
  1006. Offer `pdf-annot-color-history' as default values."
  1007. (let* ((defaults (append
  1008. (delq nil
  1009. (list
  1010. (cdr (assq 'color
  1011. pdf-annot-default-markup-annotation-properties))
  1012. (cdr (assq 'color
  1013. pdf-annot-default-text-annotation-properties))))
  1014. pdf-annot-color-history))
  1015. (prompt
  1016. (format "%s%s: "
  1017. (or prompt "Color")
  1018. (if defaults (format " (default %s)" (car defaults)) "")))
  1019. (current-completing-read-function completing-read-function)
  1020. (completing-read-function
  1021. (lambda (prompt collection &optional predicate require-match
  1022. initial-input _hist _def inherit-input-method)
  1023. (funcall current-completing-read-function
  1024. prompt collection predicate require-match
  1025. initial-input 'pdf-annot-color-history
  1026. defaults
  1027. inherit-input-method))))
  1028. (read-color prompt)))
  1029. (defun pdf-annot-merge-alists (&rest alists)
  1030. "Merge ALISTS into a single one.
  1031. Suppresses successive duplicate entries of keys after the first
  1032. occurrence in ALISTS."
  1033. (let (merged)
  1034. (dolist (elt (apply 'append alists))
  1035. (unless (assq (car elt) merged)
  1036. (push elt merged)))
  1037. (nreverse merged)))
  1038. ;; * ================================================================== *
  1039. ;; * Displaying annotation contents
  1040. ;; * ================================================================== *
  1041. (defun pdf-annot-print-property (a property)
  1042. "Pretty print annotation A's property PROPERTY."
  1043. (let ((value (pdf-annot-get a property)))
  1044. (cl-case property
  1045. (color
  1046. (propertize (or value "")
  1047. 'face (and value
  1048. `(:background ,value))))
  1049. ((created modified)
  1050. (let ((date value))
  1051. (if (null date)
  1052. "No date"
  1053. (current-time-string date))))
  1054. ;; print verbatim
  1055. (subject
  1056. (or value "No subject"))
  1057. (opacity
  1058. (let ((opacity (or value 1.0)))
  1059. (format "%d%%" (round (* 100 opacity)))))
  1060. (t (format "%s" (or value ""))))))
  1061. (defun pdf-annot-print-annotation (a)
  1062. "Pretty print annotation A."
  1063. (or (run-hook-with-args-until-success
  1064. 'pdf-annot-print-annotation-functions a)
  1065. (pdf-annot-print-annotation-default a)))
  1066. (defun pdf-annot-print-annotation-default (a)
  1067. "Default pretty printer for annotation A.
  1068. The result consists of a header (as printed with
  1069. `pdf-annot-print-annotation-header') a newline and A's contents
  1070. property."
  1071. (concat
  1072. (pdf-annot-print-annotation-header a)
  1073. "\n"
  1074. (pdf-annot-get a 'contents)))
  1075. (defun pdf-annot-print-annotation-header (a)
  1076. "Emit a suitable header string for annotation A."
  1077. (let ((header
  1078. (cond
  1079. ((eq 'file (pdf-annot-get a 'type))
  1080. (let ((att (pdf-annot-get-attachment a)))
  1081. (format "File attachment `%s' of %s"
  1082. (or (cdr (assq 'filename att)) "unnamed")
  1083. (if (cdr (assq 'size att))
  1084. (format "size %s" (file-size-human-readable
  1085. (cdr (assq 'size att))))
  1086. "unknown size"))))
  1087. (t
  1088. (format "%s"
  1089. (mapconcat
  1090. 'identity
  1091. (mapcar
  1092. (lambda (property)
  1093. (pdf-annot-print-property
  1094. a property))
  1095. `(subject
  1096. label
  1097. modified))
  1098. ";"))))))
  1099. (setq header (propertize header 'face 'header-line
  1100. 'intangible t 'read-only t))
  1101. ;; This `trick' makes the face apply in a tooltip.
  1102. (propertize header 'display header)))
  1103. (defun pdf-annot-print-annotation-latex-maybe (a)
  1104. "Maybe print annotation A's content as a LaTeX fragment.
  1105. See `pdf-annot-latex-string-predicate'."
  1106. (when (and (functionp pdf-annot-latex-string-predicate)
  1107. (funcall pdf-annot-latex-string-predicate
  1108. (pdf-annot-get a 'contents)))
  1109. (pdf-annot-print-annotation-latex a)))
  1110. (defun pdf-annot-print-annotation-latex (a)
  1111. "Print annotation A's content as a LaTeX fragment.
  1112. This compiles A's contents as a LaTeX fragment and puts the
  1113. resulting image as a display property on the contents, prefixed
  1114. by a header."
  1115. (let (tempfile)
  1116. (unwind-protect
  1117. (with-current-buffer (pdf-annot-get-buffer a)
  1118. (let* ((page (pdf-annot-get a 'page))
  1119. (header (pdf-annot-print-annotation-header a))
  1120. (contents (pdf-annot-get a 'contents))
  1121. (hash (sxhash (format
  1122. "pdf-annot-print-annotation-latex%s%s%s"
  1123. page header contents)))
  1124. (data (pdf-cache-lookup-image page 0 nil hash))
  1125. (org-format-latex-header
  1126. pdf-annot-latex-header)
  1127. (temporary-file-directory
  1128. (pdf-util-expand-file-name "pdf-annot-print-annotation-latex")))
  1129. (unless (file-directory-p temporary-file-directory)
  1130. (make-directory temporary-file-directory))
  1131. (unless data
  1132. (setq tempfile (make-temp-file "pdf-annot" nil ".png"))
  1133. ;; FIXME: Why is this with-temp-buffer needed (which it is) ?
  1134. (with-temp-buffer
  1135. (org-create-formula-image
  1136. contents tempfile org-format-latex-options t))
  1137. (setq data (pdf-util-munch-file tempfile))
  1138. (if (and (> (length data) 3)
  1139. (equal (substring data 1 4)
  1140. "PNG"))
  1141. (pdf-cache-put-image page 0 data hash)
  1142. (setq data nil)))
  1143. (concat
  1144. header
  1145. "\n"
  1146. (if data
  1147. (propertize
  1148. contents 'display (pdf-view-create-image data))
  1149. (propertize
  1150. contents
  1151. 'display
  1152. (concat
  1153. (propertize "Failed to compile latex fragment\n"
  1154. 'face 'error)
  1155. contents))))))
  1156. (when (and tempfile
  1157. (file-exists-p tempfile))
  1158. (delete-file tempfile)))))
  1159. ;; * ================================================================== *
  1160. ;; * Editing annotation contents
  1161. ;; * ================================================================== *
  1162. (defvar-local pdf-annot-edit-contents--annotation nil)
  1163. (put 'pdf-annot-edit-contents--annotation 'permanent-local t)
  1164. (defvar-local pdf-annot-edit-contents--buffer nil)
  1165. (defcustom pdf-annot-edit-contents-setup-function
  1166. (lambda (a)
  1167. (let ((mode (if (funcall pdf-annot-latex-string-predicate
  1168. (pdf-annot-get a 'contents))
  1169. 'latex-mode
  1170. 'text-mode)))
  1171. (unless (derived-mode-p mode)
  1172. (funcall mode))))
  1173. "A function for setting up, e.g. the major-mode, of the edit buffer.
  1174. The function receives one argument, the annotation whose contents
  1175. is about to be edited in this buffer.
  1176. The default value turns on `latex-mode' if
  1177. `pdf-annot-latex-string-predicate' returns non-nil on the
  1178. annotation's contents and otherwise `text-mode'. "
  1179. :group 'pdf-annot
  1180. :type 'function)
  1181. (defcustom pdf-annot-edit-contents-display-buffer-action
  1182. '((display-buffer-reuse-window
  1183. display-buffer-split-below-and-attach)
  1184. (inhibit-same-window . t)
  1185. (window-height . 0.25))
  1186. "Display action when showing the edit buffer."
  1187. :group 'pdf-annot
  1188. :type display-buffer--action-custom-type)
  1189. (defvar pdf-annot-edit-contents-minor-mode-map
  1190. (let ((kmap (make-sparse-keymap)))
  1191. (set-keymap-parent kmap text-mode-map)
  1192. (define-key kmap (kbd "C-c C-c") 'pdf-annot-edit-contents-commit)
  1193. (define-key kmap (kbd "C-c C-q") 'pdf-annot-edit-contents-abort)
  1194. kmap))
  1195. (define-minor-mode pdf-annot-edit-contents-minor-mode
  1196. "Active when editing the contents of annotations."
  1197. nil nil nil
  1198. (when pdf-annot-edit-contents-minor-mode
  1199. (message "%s"
  1200. (substitute-command-keys
  1201. "Press \\[pdf-annot-edit-contents-commit] to commit your changes, \\[pdf-annot-edit-contents-abort] to abandon them."))))
  1202. (put 'pdf-annot-edit-contents-minor-mode 'permanent-local t)
  1203. ;; FIXME: Document pdf-annot-edit-* functions below.
  1204. (defun pdf-annot-edit-contents-finalize (do-save &optional do-kill)
  1205. (when (buffer-modified-p)
  1206. (cond
  1207. ((eq do-save 'ask)
  1208. (save-window-excursion
  1209. (display-buffer (current-buffer) nil (selected-frame))
  1210. (when (y-or-n-p "Save changes to this annotation ?")
  1211. (pdf-annot-edit-contents-save-annotation))))
  1212. (do-save
  1213. (pdf-annot-edit-contents-save-annotation)))
  1214. (set-buffer-modified-p nil))
  1215. (dolist (win (get-buffer-window-list))
  1216. (quit-window do-kill win)))
  1217. (defun pdf-annot-edit-contents-save-annotation ()
  1218. (when pdf-annot-edit-contents--annotation
  1219. (pdf-annot-put pdf-annot-edit-contents--annotation
  1220. 'contents
  1221. (buffer-substring-no-properties (point-min) (point-max)))
  1222. (set-buffer-modified-p nil)))
  1223. (defun pdf-annot-edit-contents-commit ()
  1224. (interactive)
  1225. (pdf-annot-edit-contents-finalize t))
  1226. (defun pdf-annot-edit-contents-abort ()
  1227. (interactive)
  1228. (pdf-annot-edit-contents-finalize nil t))
  1229. (defun pdf-annot-edit-contents-noselect (a)
  1230. (with-current-buffer (pdf-annot-get-buffer a)
  1231. (when (and (buffer-live-p pdf-annot-edit-contents--buffer)
  1232. (not (eq a pdf-annot-edit-contents--annotation)))
  1233. (with-current-buffer pdf-annot-edit-contents--buffer
  1234. (pdf-annot-edit-contents-finalize 'ask)))
  1235. (unless (buffer-live-p pdf-annot-edit-contents--buffer)
  1236. (setq pdf-annot-edit-contents--buffer
  1237. (with-current-buffer (get-buffer-create
  1238. (format "*Edit Annotation %s*"
  1239. (buffer-name)))
  1240. (pdf-annot-edit-contents-minor-mode 1)
  1241. (current-buffer))))
  1242. (with-current-buffer pdf-annot-edit-contents--buffer
  1243. (let ((inhibit-read-only t))
  1244. (erase-buffer)
  1245. (save-excursion (insert (pdf-annot-get a 'contents)))
  1246. (set-buffer-modified-p nil))
  1247. (setq pdf-annot-edit-contents--annotation a)
  1248. (funcall pdf-annot-edit-contents-setup-function a)
  1249. (current-buffer))))
  1250. (defun pdf-annot-edit-contents (a)
  1251. (select-window
  1252. (display-buffer
  1253. (pdf-annot-edit-contents-noselect a)
  1254. pdf-annot-edit-contents-display-buffer-action)))
  1255. (defun pdf-annot-edit-contents-mouse (ev)
  1256. (interactive "@e")
  1257. (let* ((pos (posn-object-x-y (event-start ev)))
  1258. (a (and pos (pdf-annot-at-position pos))))
  1259. (unless a
  1260. (error "No annotation at this position"))
  1261. (pdf-annot-edit-contents a)))
  1262. ;; * ================================================================== *
  1263. ;; * Listing annotations
  1264. ;; * ================================================================== *
  1265. (defcustom pdf-annot-list-display-buffer-action
  1266. '((display-buffer-reuse-window
  1267. display-buffer-pop-up-window)
  1268. (inhibit-same-window . t))
  1269. "Display action used when displaying the list buffer."
  1270. :group 'pdf-annot
  1271. :type display-buffer--action-custom-type)
  1272. (defcustom pdf-annot-list-format
  1273. '((page . 3)
  1274. (type . 10)
  1275. (label . 24)
  1276. (date . 24))
  1277. "Annotation properties visible in the annotation list.
  1278. It should be a list of \(PROPERTIZE. WIDTH\), where PROPERTY is a
  1279. symbol naming one of supported properties to list and WIDTH its
  1280. desired column-width.
  1281. Currently supported properties are page, type, label, date and contents."
  1282. :type '(alist :key-type (symbol))
  1283. :options '((page (integer :value 3 :tag "Column Width"))
  1284. (type (integer :value 10 :tag "Column Width" ))
  1285. (label (integer :value 24 :tag "Column Width"))
  1286. (date (integer :value 24 :tag "Column Width"))
  1287. (contents (integer :value 56 :tag "Column Width")))
  1288. :group 'pdf-annot)
  1289. (defcustom pdf-annot-list-highlight-type nil
  1290. "Whether to highlight \"Type\" column annotation list with annotation color."
  1291. :group 'pdf-annot
  1292. :type 'boolean)
  1293. (defvar-local pdf-annot-list-buffer nil)
  1294. (defvar-local pdf-annot-list-document-buffer nil)
  1295. (defvar pdf-annot-list-mode-map
  1296. (let ((km (make-sparse-keymap)))
  1297. (define-key km (kbd "C-c C-f") 'pdf-annot-list-follow-minor-mode)
  1298. (define-key km (kbd "SPC") 'pdf-annot-list-display-annotation-from-id)
  1299. km))
  1300. (defun pdf-annot-property-completions (property)
  1301. "Return a list of completion candidates for annotation property PROPERTY.
  1302. Return nil, if not available."
  1303. (cl-case property
  1304. (color (pdf-util-color-completions))
  1305. (icon (copy-sequence pdf-annot-standard-text-icons))))
  1306. (defun pdf-annot-compare-annotations (a1 a2)
  1307. "Compare annotations A1 and A2.
  1308. Return non-nil if A1's page is less than A2's one or if they
  1309. belong to the same page and A1 is displayed above/left of A2."
  1310. (let ((p1 (pdf-annot-get a1 'page))
  1311. (p2 (pdf-annot-get a2 'page)))
  1312. (or (< p1 p2)
  1313. (and (= p1 p2)
  1314. (let ((e1 (pdf-util-scale
  1315. (car (pdf-annot-get-display-edges a1))
  1316. '(1000 . 1000)))
  1317. (e2 (pdf-util-scale
  1318. (car (pdf-annot-get-display-edges a2))
  1319. '(1000 . 1000))))
  1320. (pdf-util-with-edges (e1 e2)
  1321. (or (< e1-top e2-top)
  1322. (and (= e1-top e2-top)
  1323. (<= e1-left e2-left)))))))))
  1324. (defun pdf-annot-list-entries ()
  1325. (unless (buffer-live-p pdf-annot-list-document-buffer)
  1326. (error "No PDF document associated with this buffer"))
  1327. (mapcar 'pdf-annot-list-create-entry
  1328. (sort (pdf-annot-getannots nil pdf-annot-list-listed-types
  1329. pdf-annot-list-document-buffer)
  1330. 'pdf-annot-compare-annotations)))
  1331. (defun pdf-annot--make-entry-formatter (a)
  1332. (lambda (fmt)
  1333. (let ((entry-type (car fmt))
  1334. (entry-width (cdr fmt))
  1335. ;; Taken from css-mode.el
  1336. (contrasty-color
  1337. (lambda (name)
  1338. (if (> (color-distance name "black") 292485)
  1339. "black" "white")))
  1340. (prune-newlines
  1341. (lambda (str)
  1342. (replace-regexp-in-string "\n" " " str t t))))
  1343. (cl-ecase entry-type
  1344. (date (pdf-annot-print-property a 'modified))
  1345. (page (pdf-annot-print-property a 'page))
  1346. (label (funcall prune-newlines
  1347. (pdf-annot-print-property a 'label)))
  1348. (contents
  1349. (truncate-string-to-width
  1350. (funcall prune-newlines
  1351. (pdf-annot-print-property a 'contents))
  1352. entry-width))
  1353. (type
  1354. (let ((color (pdf-annot-get a 'color))
  1355. (type (pdf-annot-print-property a 'type)))
  1356. (if pdf-annot-list-highlight-type
  1357. (propertize
  1358. type 'face
  1359. `(:background ,color
  1360. :foreground ,(funcall contrasty-color color)))
  1361. type)))))))
  1362. (defun pdf-annot-list-create-entry (a)
  1363. "Create a `tabulated-list-entries' entry for annotation A."
  1364. (list (pdf-annot-get-id a)
  1365. (vconcat
  1366. (mapcar (pdf-annot--make-entry-formatter a)
  1367. pdf-annot-list-format))))
  1368. (define-derived-mode pdf-annot-list-mode tablist-mode "Annots"
  1369. (let* ((page-sorter
  1370. (lambda (a b)
  1371. (< (string-to-number (aref (cadr a) 0))
  1372. (string-to-number (aref (cadr b) 0)))))
  1373. (format-generator
  1374. (lambda (format)
  1375. (let ((field (car format))
  1376. (width (cdr format)))
  1377. (cl-case field
  1378. (page `("Pg." 3 ,page-sorter :read-only t :right-alight t))
  1379. (t (list
  1380. (capitalize (symbol-name field))
  1381. width t :read-only t)))))))
  1382. (setq tabulated-list-entries 'pdf-annot-list-entries
  1383. tabulated-list-format (vconcat
  1384. (mapcar
  1385. format-generator
  1386. pdf-annot-list-format))
  1387. tabulated-list-padding 2))
  1388. (set-keymap-parent pdf-annot-list-mode-map tablist-mode-map)
  1389. (use-local-map pdf-annot-list-mode-map)
  1390. (when (assq 'type pdf-annot-list-format)
  1391. (setq tablist-current-filter
  1392. `(not (== "Type" "link"))))
  1393. (tabulated-list-init-header))
  1394. (defun pdf-annot-list-annotations ()
  1395. "List annotations in a dired like buffer.
  1396. \\{pdf-annot-list-mode-map}"
  1397. (interactive)
  1398. (pdf-util-assert-pdf-buffer)
  1399. (let ((buffer (current-buffer)))
  1400. (with-current-buffer (get-buffer-create
  1401. (format "*%s's annots*"
  1402. (file-name-sans-extension
  1403. (buffer-name))))
  1404. (delay-mode-hooks
  1405. (unless (derived-mode-p 'pdf-annot-list-mode)
  1406. (pdf-annot-list-mode))
  1407. (setq pdf-annot-list-document-buffer buffer)
  1408. (tabulated-list-print)
  1409. (setq tablist-context-window-function
  1410. (lambda (id) (pdf-annot-list-context-function id buffer))
  1411. tablist-operations-function 'pdf-annot-list-operation-function)
  1412. (let ((list-buffer (current-buffer)))
  1413. (with-current-buffer buffer
  1414. (setq pdf-annot-list-buffer list-buffer))))
  1415. (run-mode-hooks)
  1416. (pop-to-buffer
  1417. (current-buffer)
  1418. pdf-annot-list-display-buffer-action)
  1419. (tablist-move-to-major-column)
  1420. (tablist-display-context-window))
  1421. (add-hook 'pdf-info-close-document-hook
  1422. 'pdf-annot-list-update nil t)
  1423. (add-hook 'pdf-annot-modified-functions
  1424. 'pdf-annot-list-update nil t)))
  1425. (defun pdf-annot-list-goto-annotation (a)
  1426. (with-current-buffer (pdf-annot-get-buffer a)
  1427. (unless (and (buffer-live-p pdf-annot-list-buffer)
  1428. (get-buffer-window pdf-annot-list-buffer))
  1429. (pdf-annot-list-annotations))
  1430. (with-selected-window (get-buffer-window pdf-annot-list-buffer)
  1431. (goto-char (point-min))
  1432. (let ((id (pdf-annot-get-id a)))
  1433. (while (and (not (eobp))
  1434. (not (eq id (tabulated-list-get-id))))
  1435. (forward-line))
  1436. (unless (eq id (tabulated-list-get-id))
  1437. (error "Unable to find annotation"))
  1438. (when (invisible-p (point))
  1439. (tablist-suspend-filter t))
  1440. (tablist-move-to-major-column)))))
  1441. (defun pdf-annot-list-update (&optional _fn)
  1442. (when (buffer-live-p pdf-annot-list-buffer)
  1443. (with-current-buffer pdf-annot-list-buffer
  1444. (unless tablist-edit-column-minor-mode
  1445. (tablist-revert))
  1446. (tablist-context-window-update))))
  1447. (defun pdf-annot-list-context-function (id buffer)
  1448. (with-current-buffer (get-buffer-create "*Contents*")
  1449. (set-window-buffer nil (current-buffer))
  1450. (let ((inhibit-read-only t))
  1451. (erase-buffer)
  1452. (when id
  1453. (save-excursion
  1454. (insert
  1455. (pdf-annot-print-annotation
  1456. (pdf-annot-getannot id buffer)))))
  1457. (read-only-mode 1))))
  1458. (defun pdf-annot-list-operation-function (op &rest args)
  1459. (cl-ecase op
  1460. (supported-operations '(delete find-entry))
  1461. (delete
  1462. (cl-destructuring-bind (ids)
  1463. args
  1464. (when (buffer-live-p pdf-annot-list-document-buffer)
  1465. (with-current-buffer pdf-annot-list-document-buffer
  1466. (pdf-annot-with-atomic-modifications
  1467. (dolist (a (mapcar 'pdf-annot-getannot ids))
  1468. (pdf-annot-delete a)))))))
  1469. (find-entry
  1470. (cl-destructuring-bind (id)
  1471. args
  1472. (unless (buffer-live-p pdf-annot-list-document-buffer)
  1473. (error "No PDF document associated with this buffer"))
  1474. (let* ((buffer pdf-annot-list-document-buffer)
  1475. (a (pdf-annot-getannot id buffer))
  1476. (pdf-window (save-selected-window
  1477. (or (get-buffer-window buffer)
  1478. (display-buffer buffer))))
  1479. window)
  1480. (with-current-buffer buffer
  1481. (pdf-annot-activate-annotation a)
  1482. (setq window (selected-window)))
  1483. ;; Make it so that quitting the edit window returns to the
  1484. ;; list window.
  1485. (unless (memq window (list (selected-window) pdf-window))
  1486. (let* ((quit-restore
  1487. (window-parameter window 'quit-restore)))
  1488. (when quit-restore
  1489. (setcar (nthcdr 2 quit-restore) (selected-window))))))))))
  1490. (defvar pdf-annot-list-display-annotation--timer nil)
  1491. (defun pdf-annot-list-display-annotation-from-id (id)
  1492. (interactive (list (tabulated-list-get-id)))
  1493. (when id
  1494. (unless (buffer-live-p pdf-annot-list-document-buffer)
  1495. (error "PDF buffer was killed"))
  1496. (when (timerp pdf-annot-list-display-annotation--timer)
  1497. (cancel-timer pdf-annot-list-display-annotation--timer))
  1498. (setq pdf-annot-list-display-annotation--timer
  1499. (run-with-idle-timer 0.1 nil
  1500. (lambda (buffer a)
  1501. (when (buffer-live-p buffer)
  1502. (with-selected-window
  1503. (or (get-buffer-window buffer)
  1504. (display-buffer
  1505. buffer
  1506. '(nil (inhibit-same-window . t))))
  1507. (pdf-annot-show-annotation a t))))
  1508. pdf-annot-list-document-buffer
  1509. (pdf-annot-getannot id pdf-annot-list-document-buffer)))))
  1510. (define-minor-mode pdf-annot-list-follow-minor-mode
  1511. "" nil nil nil
  1512. (unless (derived-mode-p 'pdf-annot-list-mode)
  1513. (error "No in pdf-annot-list-mode."))
  1514. (cond
  1515. (pdf-annot-list-follow-minor-mode
  1516. (add-hook 'tablist-selection-changed-functions
  1517. 'pdf-annot-list-display-annotation-from-id nil t)
  1518. (let ((id (tabulated-list-get-id)))
  1519. (when id
  1520. (pdf-annot-list-display-annotation-from-id id))))
  1521. (t
  1522. (remove-hook 'tablist-selection-changed-functions
  1523. 'pdf-annot-list-display-annotation-from-id t))))
  1524. (provide 'pdf-annot)
  1525. ;;; pdf-annot.el ends here